UNPKG

@nutrient-sdk/dws-mcp-server

Version:

MCP server for Nutrient DWS Processor API

672 lines (671 loc) 35.3 kB
import { z } from 'zod'; export const CheckCreditsArgsSchema = z.object({}); export const DirectoryTreeArgsSchema = z.object({ path: z .string() .describe('The path to the directory to analyze to find documents or files a user has referenced. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), }); export const RectSchema = z .array(z.number()) .length(4) .describe('Rectangle in a form [left, top, width, height] in PDF points (1 PDF point equals 1⁄72 of an inch).'); export const PageRangeSchema = z .object({ start: z.number().int().default(0).describe('Start page index (0-based). Default is 0 (first page).'), end: z .number() .int() .default(-1) .describe('End page index (0-based). Default is -1 (last page). Negative values count from the end.'), }) .describe('Defines the range of pages in a document. The indexing starts from 0. It is possible to use negative numbers to refer to pages from the last page. For example, `-1` refers to the last page.'); export const SignatureAppearanceSchema = z.object({ mode: z .enum(['signatureOnly', 'signatureAndDescription', 'descriptionOnly']) .default('signatureAndDescription') .describe('Specifies what will be rendered in the signature appearance: graphics, description, or both.'), contentType: z .string() .optional() .describe('The content type of the watermark image when provided in the multipart request. Supported types are `application/pdf`, `image/png`, and `image/jpeg`.'), showSigner: z .boolean() .default(true) .describe('Controls whether the signer name from signatureMetadata is shown in the signature appearance.'), showReason: z .boolean() .default(false) .describe('Controls whether the signing reason from signatureMetadata is shown in the signature appearance.'), showLocation: z .boolean() .default(false) .describe('Controls whether the signing location from signatureMetadata is shown in the signature appearance.'), showWatermark: z .boolean() .default(true) .describe('Controls whether to include the watermark in the signature appearance.'), showSignDate: z .boolean() .default(true) .describe('Controls whether to show the signing date and time in the signature appearance.'), showDateTimezone: z .boolean() .default(false) .describe('Controls whether to include the timezone in the signing date.'), }); export const SignaturePositionSchema = z.object({ pageIndex: z .number() .int() .min(0) .describe('The index of the page where the signature appearance will be rendered (0-based indexing).'), rect: RectSchema.describe('The bounding box where the signature appearance will be rendered.'), }); export const SignatureMetadataSchema = z.object({ signerName: z.string().optional().describe('The name of the person or organization signing the document.'), signatureReason: z.string().optional().describe('The reason for signing the document.'), signatureLocation: z .string() .optional() .describe('The geographical or digital location where the document is being signed.'), }); export const CreateDigitalSignatureSchema = z.object({ signatureType: z.enum(['cms', 'cades']).default('cms').describe('The signature type to create.'), flatten: z.boolean().default(false).describe('Controls whether to flatten the document before signing it.'), formFieldName: z .string() .optional() .describe('Name of the signature form field to sign. Use this when signing an existing signature form field.'), appearance: SignatureAppearanceSchema.optional().describe('The appearance settings for the visible signature. Omit if you want an invisible signature to be created.'), position: SignaturePositionSchema.optional().describe('Position of the visible signature form field. Omit if you want an invisible signature or if you specified the formFieldName option.'), signatureMetadata: SignatureMetadataSchema.optional().describe('Optional metadata that describes the digital signature and becomes part of the signature itself.'), cadesLevel: z .enum(['b-lt', 'b-t', 'b-b']) .default('b-lt') .optional() .describe('The CAdES level to use when creating the signature. The default value is CAdES B-LT.'), }); export const SignAPIArgsSchema = z.object({ filePath: z .string() .describe('The path to the file to be signed. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), signatureOptions: CreateDigitalSignatureSchema.optional().describe('Options for creating the digital signature. If not provided, defaults will be used.'), watermarkImagePath: z .string() .optional() .describe("The path to the watermark image to be used as part of the signature's appearance. Optional. Resolves to sandbox path if enabled, otherwise resolves to the local file system."), graphicImagePath: z .string() .optional() .describe("The path to the graphic image to be used as part of the signature's appearance. Optional. Resolves to sandbox path if enabled, otherwise resolves to the local file system."), outputPath: z .string() .describe('A path to the output file to. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), }); export const AiRedactArgsSchema = z.object({ filePath: z .string() .describe('The path to the document to redact. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), criteria: z .string() .min(1, 'Criteria must not be empty') .default('All personally identifiable information') .describe('What sensitive information to redact. The AI will detect and remove matching content. ' + 'Examples: "All personally identifiable information", ' + '"Social security numbers and credit card numbers", ' + '"Names, email addresses, and phone numbers", ' + '"Protected health information (PHI)".'), outputPath: z .string() .describe('Path for the redacted output file. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), stage: z .boolean() .optional() .describe('Stages redactions without applying them. By default (when neither stage nor apply is set), redactions are detected and immediately applied. Mutually exclusive with apply. Typical workflow: run once with stage=true, then later run again with apply=true to apply staged redactions.'), apply: z .boolean() .optional() .describe('Applies previously staged redactions. By default (when neither stage nor apply is set), redactions are detected and immediately applied. Mutually exclusive with stage. Typical workflow: run once with stage=true, then later run again with apply=true to apply staged redactions.'), }); export const FilePartSchema = z.object({ file: z .string() .describe('The path to the file to be processed. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), password: z.string().optional().describe('The password for the input file if it is password-protected.'), pages: PageRangeSchema.optional().describe('Page range to include from the file (0-based indexing).'), content_type: z .string() .optional() .describe("Used to determine the file type when the file content type is not available and can't be inferred."), // For simplicity, we do not allow actions to be performed on individual parts. Instead, actions can be performed on the resulting parts output. // actions: z // .array(z.lazy(() => BuildActionSchema)) // .optional() // .describe('Actions to perform on this part.'), }); export const ApplyXfdfActionSchema = z.object({ type: z.literal('applyXfdf').describe('Apply the XFDF to the document to import annotations to a document.'), file: z .string() .describe('The path to the XFDF file or a reference to a file in the multipart request. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), }); export const FlattenActionSchema = z.object({ type: z.literal('flatten').describe('Flatten the annotations in the document.'), // It's unlikely that annotation IDs are known upfront, therefore, we won't support this in the flatten action. // annotationIds: z // .array(z.union([z.string(), z.number()])) // .optional() // .describe( // 'Annotation IDs to flatten. These can be annotation IDs or pdfObjectIds. If not specified, all annotations will be flattened.', // ), }); export const OcrActionSchema = z.object({ type: z.literal('ocr').describe('Perform optical character recognition (OCR) in the document.'), language: z.string().describe('Language to be used for the OCR text extraction.'), }); export const RotateActionSchema = z.object({ type: z.literal('rotate').describe('Rotate all pages by the angle specified.'), rotateBy: z .number() .describe('The angle by which the pages should be rotated, clockwise.') .refine((val) => [90, 180, 270].includes(val), { message: 'rotateBy must be one of: 90, 180, 270', }), }); export const WatermarkDimensionSchema = z .union([ z.number().describe('Value in points'), z .string() .regex(/^\d+%$/) .describe('Percentage value'), ]) .describe('Dimension value in points or percentage'); export const BaseWatermarkPropertiesSchema = z.object({ type: z.literal('watermark').describe('Watermark action.'), watermarkType: z.enum(['text', 'image']).describe('Type of the watermark.'), width: WatermarkDimensionSchema.describe('Width of the watermark.'), height: WatermarkDimensionSchema.describe('Height of the watermark.'), rotation: z.number().optional().default(0).describe('Rotation of the watermark in counterclockwise degrees.'), opacity: z .number() .min(0) .max(1) .optional() .describe('Watermark opacity. 0 is fully transparent, 1 is fully opaque. 0.7 is a optimal value.'), text: z.string().optional().describe('Text used for watermarking'), fontColor: z.string().optional().describe('A hex color of the watermark text. ^#[0-9a-fA-F]{6}$'), image: z .string() .optional() .describe('For image watermarks, the path to the image file or a reference to a file in the multipart request. Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), // For simplicity, we apply the watermark to the center of the page. // top: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the top edge of a page.'), // right: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the right edge of a page.'), // bottom: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the bottom edge of a page.'), // left: WatermarkDimensionSchema.optional().describe('Offset of the watermark from the left edge of a page.'), // For simplicity, we do not support custom fonts. // fontFamily: z.string().optional().describe('The font to render the text.'), // fontSize: z.number().optional().describe('Size of the text in points.'), // fontStyle: z // .array(z.enum(['bold', 'italic'])) // .optional() // .describe('Text style. Can be only italic, only bold, italic and bold, or none of these.'), }); export const SearchPresetSchema = z.enum([ 'credit-card-number', 'date', 'email-address', 'international-phone-number', 'ipv4', 'ipv6', 'mac-address', 'north-american-phone-number', 'social-security-number', 'time', 'url', 'us-zip-code', 'vin', ]).describe(` - credit-card-number — matches a number with 13 to 19 digits that begins with 16. Spaces and - are allowed anywhere in the number. - date — matches date formats such as mm/dd/yyyy, mm/dd/yy, dd/mm/yyyy, and dd/mm/yy. It rejects any days greater than 31 or months greater than 12 and accepts a leading 0 in front of a single-digit day or month. The delimiter can be -, ., or /. - email-address — matches an email address. Expects the format of *@*.* with at least two levels of the domain name. - international-phone-number — matches international phone numbers. The number can have 7 to 15 digits with spaces or - occurring anywhere within the number, and it must have prefix of + or 00. - ipv4 — matches an IPv4 address with an optional mask at the end. - ipv6 — matches a full and compressed IPv6 address as defined in RFC 2373. - mac-address — matches a MAC address with either - or : as a delimiter. - north-american-phone-number — matches North American-style phone numbers. NANPA standardization is used with international support. - social-security-number — matches a social security number. Expects the format of XXX-XX-XXXX or XXXXXXXXX, with X denoting digits. - time — matches time formats such as 00:00:00, 00:00, and 00:00 PM. 12- and 24-hour formats are allowed. Seconds and AM/PM denotation are both optional. - url — matches a URL with a prefix of http or https, with an optional subdomain. - us-zip-code — matches a USA-style zip code. The format expected is XXXXX, XXXXX-XXXX or XXXXX/XXXX. - vin — matches US and ISO Standard 3779 Vehicle Identification Number. The format expects 17 characters, with the last 5 characters being numeric. I, i, O, o, Q, q, and _ characters are not allowed. `); const BaseStrategyOptionsSchema = z.object({ includeAnnotations: z .boolean() .default(true) .describe('Determines if redaction annotations are created on top of annotations whose content match the provided preset.'), start: z.number().int().default(0).describe('The index of the page from where you want to start the search.'), limit: z .number() .int() .nullable() .default(null) .describe('Starting from start, the number of pages to search. Default is to the end of the document.'), }); export const CreateRedactionsStrategyOptionsPresetSchema = BaseStrategyOptionsSchema.extend({ preset: SearchPresetSchema, }); export const CreateRedactionsStrategyOptionsRegexSchema = BaseStrategyOptionsSchema.extend({ regex: z.string().describe('Regex search term used for searching for text to redact.'), caseSensitive: z.boolean().default(true).describe('Determines if the search will be case sensitive.'), }); export const CreateRedactionsStrategyOptionsTextSchema = BaseStrategyOptionsSchema.extend({ text: z.string().describe('Search term used for searching for text to redact.'), caseSensitive: z.boolean().default(false).describe('Determines if the search will be case sensitive.'), }); export const CreateRedactionsActionSchema = z.object({ type: z .literal('createRedactions') .describe('Creates redactions according to the given strategy. Once redactions are created, they need to be applied using the applyRedactions action.'), strategy: z.enum(['preset', 'regex', 'text']).describe('The strategy to use for creating redactions.'), strategyOptions: z .union([ CreateRedactionsStrategyOptionsPresetSchema, CreateRedactionsStrategyOptionsRegexSchema, CreateRedactionsStrategyOptionsTextSchema, ]) .describe('Options for the selected strategy.'), }); export const ApplyRedactionsActionSchema = z.object({ type: z.literal('applyRedactions').describe('Applies the redactions created by an earlier createRedactions action.'), }); export const BuildActionSchema = z.discriminatedUnion('type', [ // For now, we will not support applying Instant JSON. // ApplyInstantJsonActionSchema, ApplyXfdfActionSchema, FlattenActionSchema, OcrActionSchema, RotateActionSchema, BaseWatermarkPropertiesSchema, CreateRedactionsActionSchema, ApplyRedactionsActionSchema, ]); export const MetadataSchema = z.object({ title: z.string().optional().describe('The document title.'), author: z.string().optional().describe('The document author.'), }); export const LabelSchema = z.object({ pages: PageRangeSchema.describe('Page range to apply the label to (0-based indexing).'), label: z.string().describe('The label to apply to specified pages.'), }); export const OptimizePdfSchema = z.object({ grayscaleText: z.boolean().optional().default(false).describe('Convert text to grayscale.'), grayscaleGraphics: z.boolean().optional().default(false).describe('Convert graphics to grayscale.'), grayscaleImages: z.boolean().optional().default(false).describe('Convert images to grayscale.'), grayscaleFormFields: z.boolean().optional().default(false).describe('Convert form fields to grayscale.'), grayscaleAnnotations: z.boolean().optional().default(false).describe('Convert annotations to grayscale.'), disableImages: z.boolean().optional().default(false).describe('Disable images in the document.'), mrcCompression: z.boolean().optional().default(false).describe('Use MRC compression.'), imageOptimizationQuality: z.number().min(1).max(4).default(2).describe('Image optimization quality.'), linearize: z.boolean().optional().default(false).describe('Linearize the PDF for faster loading over the network.'), }); export const BasePDFOutputSchema = z.object({ metadata: MetadataSchema.optional().describe('Document metadata.'), labels: z.array(LabelSchema).optional().describe('Page labels.'), user_password: z.string().optional().describe('Password required to open the document.'), owner_password: z.string().optional().describe('Password required to modify the document.'), user_permissions: z .array(z.enum([ 'printing', 'modification', 'extract', 'annotations_and_forms', 'fill_forms', 'extract_accessibility', 'assemble', 'print_high_quality', ])) .optional() .describe('Permissions granted when the document is opened with the user password.'), optimize: OptimizePdfSchema.optional().describe('PDF optimization options.'), }); export const PDFOutputSchema = BasePDFOutputSchema.extend({ type: z.literal('pdf').describe('Output as standard PDF.'), }); export const PDFAOutputSchema = BasePDFOutputSchema.extend({ type: z.literal('pdfa').describe('Output as PDF/A for archiving.'), conformance: z .enum(['pdfa-1a', 'pdfa-1b', 'pdfa-2a', 'pdfa-2u', 'pdfa-2b', 'pdfa-3a', 'pdfa-3u']) .optional() .describe('PDF/A conformance level.'), vectorization: z .boolean() .optional() .default(true) .describe('Produce vector-based graphic elements where applicable.'), rasterization: z .boolean() .optional() .default(true) .describe('Produce raster-based graphic elements where applicable.'), }); export const ImageOutputSchema = z.object({ type: z.literal('image').describe('Output as image.'), format: z.enum(['png', 'jpeg', 'jpg', 'webp']).optional().default('png').describe('Image format.'), pages: PageRangeSchema.optional().describe('Page range to render (0-based indexing).'), width: z .number() .optional() .describe('Width of the rendered image in pixels Only one of width, height, or dpi can be defined.'), height: z .number() .optional() .describe('Height of the rendered image in pixels. Only one of width, height, or dpi can be defined.'), dpi: z .number() .optional() .describe('Resolution of the rendered image in dots per inch. Only one of width, height, or dpi can be defined.'), }); export const PDFUAOutputSchema = BasePDFOutputSchema.extend({ type: z.literal('pdfua').describe('Output as PDF/UA for accessibility.'), }); export const JSONContentOutputSchema = z.object({ type: z.literal('json-content').describe('Output as JSON with document contents.'), plainText: z.boolean().optional().default(true).describe('Extract document text. Text is extracted via OCR process.'), keyValuePairs: z .boolean() .optional() .default(false) .describe('Extract key-value pairs detected within the document contents. Example of detected values are phone numbers, email addresses, currencies, numbers, dates, etc. Use one of `plainText`, `keyValuePairs`, or `tables`. at a time.'), tables: z .boolean() .optional() .default(true) .describe('Extract tabular data from the document. Use one of `plainText`, `keyValuePairs`, or `tables`. at a time.'), language: z .union([ z.string().describe('Language for OCR text extraction.'), z.array(z.string()).describe('Languages for OCR text extraction.'), ]) .optional(), // Structure text uses many chars, and often overflows the context length of an LLM. We will not support this for now. // structuredText: z.boolean().optional().default(false).describe('Extracts text with positional data.'), }); export const OfficeOutputSchema = z.object({ type: z.enum(['docx', 'xlsx', 'pptx']).describe('Output as Office document.'), }); export const HTMLOutputSchema = z.object({ type: z.literal('html').describe('Output as HTML.'), layout: z .enum(['page', 'reflow']) .optional() .describe('The layout type to use for conversion to HTML. ' + '`page` layout keeps the original structure of the document, segmented by page. ' + '`reflow` layout converts the document into a continuous flow of text, without page breaks.'), }); export const MarkdownOutputSchema = z.object({ type: z.literal('markdown').describe('Output as Markdown.'), }); export const BuildOutputSchema = z.discriminatedUnion('type', [ PDFOutputSchema, PDFAOutputSchema, PDFUAOutputSchema, ImageOutputSchema, JSONContentOutputSchema, OfficeOutputSchema, HTMLOutputSchema, MarkdownOutputSchema, ]); const InstructionsSchema = z.object({ parts: z.array(FilePartSchema).describe('Parts of the document to be built.'), actions: z .array(BuildActionSchema) .optional() .describe('Actions to be performed on the document after it is built from the parts.'), output: BuildOutputSchema.optional().describe('Output format configuration. Supports PDF, PDF/A, PDF/UA, image, JSON content, Office document formats, HTML, and Markdown.'), }); export const BuildAPIArgsSchema = z.object({ instructions: InstructionsSchema.describe('Build instructions.'), outputPath: z .string() .describe('A path to the output file to. (if required) Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), }); // ----- Data Extraction API (POST /extraction/parse) ----- // // Cross-field rules (exactly one of filePath/url; spatial format requires // outputPath; text mode supports markdown only; format and formats are // mutually exclusive; maxLanguages/maxScripts require an unset language) are // enforced in the handler rather than via a top-level `.superRefine`, because // tools are registered with `Schema.shape`, which only exists on a plain // ZodObject (a refined schema would be a ZodEffects). export const ExtractionModeSchema = z .enum(['text', 'structure', 'understand', 'agentic']) .describe('Processing mode (cost/quality trade-off). ' + 'text: fast Markdown from digital-born documents, no OCR (1 credit/page). ' + 'structure: OCR-based spatial elements (1.5 credits/page). ' + 'understand: AI-augmented spatial extraction, the default (9 credits/page). ' + 'agentic: VLM-augmented for the most complex documents (18 credits/page).'); export const ExtractionFormatSchema = z .enum(['spatial', 'markdown']) .describe('Output format. spatial: typed elements with bounding boxes, confidence, and reading order — written to outputPath. ' + 'markdown: whole-document Markdown returned inline. text mode supports markdown only; other modes default to spatial.'); export const ParseDocumentArgsSchema = z.object({ filePath: z .string() .optional() .describe('Path to the document to extract from (PDF, image, or Office file). Exactly one of filePath or url is required. ' + 'Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), url: z .string() .url() .optional() .describe('URL of the document to extract from — fetched directly by the API instead of uploading a local file. ' + 'Exactly one of filePath or url is required.'), mode: ExtractionModeSchema.optional().default('understand'), format: ExtractionFormatSchema.optional().describe('Single output format. Defaults to markdown for text mode and spatial for all other modes. Mutually exclusive with formats.'), formats: z .array(ExtractionFormatSchema) .min(1) .optional() .describe('Request multiple output formats in one call, e.g. ["spatial", "markdown"] — both output.elements and output.markdown ' + 'are returned, billed the same as a single format (no extra credits). Mutually exclusive with format.'), includeWords: z .boolean() .optional() .describe('Include word-level bounding boxes in spatial output. Server default: false. Ignored for markdown output.'), language: z .union([z.string(), z.array(z.string())]) .optional() .describe('OCR language(s) — full name (e.g. "german"), ISO code (e.g. "deu"), or array for multilingual docs. ' + 'Only applies to structure/understand/agentic modes; ignored for text mode. Leave unset for auto-detection.'), maxLanguages: z .number() .int() .min(1) .optional() .describe('Maximum number of languages to auto-detect. Only valid when language is left unset (auto-detect), and not in text mode, ' + 'which does no OCR. Server default: 2.'), maxScripts: z .number() .int() .min(1) .optional() .describe('Maximum number of scripts to auto-detect. Only valid when language is left unset (auto-detect), and not in text mode, ' + 'which does no OCR. Server default: 2.'), useHtmlTables: z .boolean() .optional() .describe('Markdown only. Render tables as HTML instead of Markdown table syntax. Server default: true.'), enableSemanticBlockFormatting: z .boolean() .optional() .describe('Markdown only. Group related content into semantic blocks. Server default: true.'), includeHeadersAndFooters: z .boolean() .optional() .describe('Markdown only. Include page headers and footers in the Markdown output. Server default: false.'), extractWordsFromPictures: z .boolean() .optional() .describe('Markdown only. Extract words found inside pictures into the Markdown output. Server default: false.'), outputPath: z .string() .optional() .describe('Where to write spatial JSON output. Required when spatial is among the requested formats (the element list can be large and is kept out of the conversation). ' + 'Resolves to sandbox path if enabled.'), }); // ----- Data Extraction API (POST /extraction/extract) ----- // // Cross-field rules (exactly one of filePath/url; maxLanguages/maxScripts // require an unset language) are enforced in the handler, for the same // ZodObject-vs-ZodEffects reason noted above the parse-endpoint schemas. export const ExtractFieldsModeSchema = z .enum(['structure', 'understand', 'agentic']) .describe('Parse mode feeding the extraction (cost/quality trade-off). No text mode — schema-guided extraction needs the ' + 'structural parse (OCR/layout) that text mode skips. structure: OCR-based spatial parse (1.5 credits/page). ' + 'understand: AI-augmented, the default (9 credits/page). agentic: VLM-augmented for the most complex documents ' + '(18 credits/page). Total cost per page is this parse component plus a fixed extract component, in Data Extraction credits.'); export const ExtractJsonSchemaSchema = z .object({ type: z.literal('object').describe('The schema root must be an object.'), description: z.string().optional().describe('Optional description of the object.'), properties: z .record(z.unknown()) .describe('Field definitions. Supported keywords only: type, properties, items, description, required, string enum, ' + 'and format: "date". $ref/$defs and composition/conditional keywords (allOf/anyOf/oneOf/if/then/else) are rejected.'), required: z.array(z.string()).optional().describe('Names of properties that must be present.'), }) .describe('JSON Schema describing the fields to extract. Root must be type: "object" with properties. Schemas are closed — ' + 'do NOT set additionalProperties yourself, the API rejects a schema that sets it. Limits: 32 KB serialized, ' + '500 fields, 50 properties per object, 5 nesting levels, enum values capped at 50.'); export const ExtractFieldsArgsSchema = z.object({ filePath: z .string() .optional() .describe('Path to the document to extract from (PDF, image, or Office file). Exactly one of filePath or url is required. ' + 'Resolves to sandbox path if enabled, otherwise resolves to the local file system.'), url: z .string() .url() .optional() .describe('URL of the document to extract from — fetched directly by the API instead of uploading a local file. ' + 'Exactly one of filePath or url is required.'), schema: ExtractJsonSchemaSchema, instructions: z .string() .max(10000) .optional() .describe('Free-text guidance for the extraction, e.g. clarifying an ambiguous field. Max 10000 characters.'), mode: ExtractFieldsModeSchema.optional().default('understand'), language: z .union([z.string(), z.array(z.string())]) .optional() .describe('OCR language(s) — full name (e.g. "german"), ISO code (e.g. "deu"), or array for multilingual docs. ' + 'Leave unset for auto-detection.'), maxLanguages: z .number() .int() .min(1) .optional() .describe('Maximum number of languages to auto-detect. Only valid when language is left unset. Server default: 2.'), maxScripts: z .number() .int() .min(1) .optional() .describe('Maximum number of scripts to auto-detect. Only valid when language is left unset. Server default: 2.'), includeCitations: z .boolean() .optional() .describe('Include per-field citations (bounding box, confidence, match quality) in output.metadata. Server default: ' + 'true. Leave unset to keep that default — passing false here disables citations.'), strict: z .boolean() .optional() .describe('Fail the extraction if a required field cannot be found, instead of returning a partial result. Server default: false.'), multimodal: z .boolean() .optional() .describe('Use multimodal (vision) extraction for higher accuracy on visual fields. Increases cost and latency. Server default: false.'), outputPath: z .string() .optional() .describe('Where to write the full response (data, per-field citations, and pages). Resolves to sandbox path if enabled. ' + 'output.data is always returned inline regardless of this option; set it to also keep the citations.'), }); // ----- Data Extraction API response shapes ----- // // Partial views over the response bodies — only the fields the handlers read, // every field optional so a benign API change degrades to "field absent" rather // than throwing. Defined as schemas for the inferred types; the handlers keep // their explicit shape guards and `JSON.parse(...) as X` assertions (not runtime // validated). const DataExtractionCreditsSchema = z.object({ cost: z.number().optional(), remainingCredits: z.number().optional(), }); export const SpatialElementSchema = z.object({ type: z.string().optional(), role: z.string().optional(), confidence: z.number().optional(), bounds: z.object({ x: z.number(), y: z.number(), width: z.number(), height: z.number() }).optional(), page: z .object({ pageIndex: z.number().optional(), pageNumber: z.number().optional(), width: z.number().optional(), height: z.number().optional(), }) .optional(), }); export const ExtractionResponseSchema = z.object({ output: z.object({ elements: z.array(SpatialElementSchema).optional(), markdown: z.string().optional() }).optional(), metrics: z.object({ pagesProcessed: z.number().optional() }).optional(), usage: z.object({ data_extraction_credits: DataExtractionCreditsSchema.optional() }).optional(), }); // Deliberately not the Processor's error shape — carries errorMessage/errorDetails // where the Processor sends `details`, so the shared handler leaves it to us. export const ExtractionErrorResponseSchema = z.object({ status: z.number().optional(), requestId: z.string().optional(), errorMessage: z.string().optional(), runId: z.string().optional(), errorDetails: z .object({ source: z.string().optional(), code: z.string().optional(), failingPaths: z.array(z.object({ path: z.string().optional(), details: z.string().optional() })).optional(), }) .optional(), }); export const PriceComponentSchema = z.object({ units: z.number().optional(), unit_cost: z.number().optional(), cost: z.number().optional(), currency: z.string().optional(), }); /** Credit usage reported in the success message — shared by /extraction/parse and /extraction/extract. */ export const CreditUsageResponseSchema = z.object({ usage: z .object({ data_extraction_credits: DataExtractionCreditsSchema.optional(), // Only present on /extraction/extract — the parse component plus the fixed extract component. price_composition: z .object({ parse: PriceComponentSchema.optional(), extract: PriceComponentSchema.optional() }) .optional(), }) .optional(), }); export const ExtractFieldsResponseSchema = CreditUsageResponseSchema.extend({ output: z.object({ data: z.unknown(), metadata: z.unknown(), pages: z.array(z.unknown()).optional() }).optional(), });