@juspay/neurolink 12.30.2 → 12.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,4 +4,4 @@ import type { OptionSchema, TextGenerationOptions } from "../../types/index.js";
4
4
  * This object provides metadata for validation and help text in the CLI loop.
5
5
  * It is derived from the main TextGenerationOptions interface to ensure consistency.
6
6
  */
7
- export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "videoOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder" | "toolRoots">, OptionSchema>;
7
+ export declare const textGenerationOptionsSchema: Record<keyof Omit<TextGenerationOptions, "prompt" | "input" | "schema" | "tools" | "context" | "conversationHistory" | "conversationMessages" | "conversationMemoryConfig" | "originalPrompt" | "middleware" | "expectedOutcome" | "evaluationCriteria" | "region" | "csvOptions" | "pdfOptions" | "imageOptions" | "videoOptions" | "officeOptions" | "tts" | "stt" | "thinkingConfig" | "requestId" | "fileRegistry" | "abortSignal" | "toolFilter" | "excludeTools" | "toolChoice" | "prepareStep" | "credentials" | "onFinish" | "onError" | "processors" | "piiDetection" | "responseValidation" | "inputValidation" | "toolExecutionCapture" | "toolExecutionRecorder" | "toolRoots">, OptionSchema>;
@@ -158,23 +158,28 @@ export async function setCodexProxySettings(baseUrl) {
158
158
  logger.warn(`[proxy] Codex: config.toml declares [${duplicate}] more than once; leaving it untouched`);
159
159
  return false;
160
160
  }
161
- // Snapshot the user's original selector once (survives crashes/restarts).
162
- if (!fs.existsSync(getCodexSnapshotPath())) {
163
- // Same preamble boundary the edit paths use. Matching document-wide would
164
- // capture a `model_provider` belonging to a table — a legacy
165
- // `[profiles.<name>]` block, say — and the restore would then write that
166
- // profile's provider back as the top-level selector.
167
- let providerMatch = null;
168
- editTomlPreamble(original, (preamble) => {
169
- providerMatch = preamble.match(CODEX_PROVIDER_LINE_RE);
170
- return null;
171
- });
172
- // Ignore a stale managed selector line if present in the original.
173
- const originalProviderLine = providerMatch && !/"neurolink"/.test(providerMatch[0])
174
- ? providerMatch[0]
175
- : null;
161
+ // Same preamble boundary the edit paths use. Matching document-wide would
162
+ // capture a `model_provider` belonging to a table — a legacy
163
+ // `[profiles.<name>]` block, say — and the restore would then write that
164
+ // profile's provider back as the top-level selector.
165
+ let providerMatch = null;
166
+ editTomlPreamble(original, (preamble) => {
167
+ providerMatch = preamble.match(CODEX_PROVIDER_LINE_RE);
168
+ return null;
169
+ });
170
+ // A selector naming "neurolink" is the one the proxy writes, possibly left
171
+ // by an earlier run, and never the user's choice.
172
+ const userProviderLine = providerMatch && !/"neurolink"/.test(providerMatch[0])
173
+ ? providerMatch[0]
174
+ : null;
175
+ const selectorIsOurs = providerMatch !== null && userProviderLine === null;
176
+ // Snapshot on first touch, and again whenever the top-level selector is
177
+ // not the proxy's. An unclean exit skips restore and leaves the snapshot
178
+ // behind; a provider the user then picks by hand, or a selector they
179
+ // remove, is newer than that snapshot and is what restore must bring back.
180
+ if (!fs.existsSync(getCodexSnapshotPath()) || !selectorIsOurs) {
176
181
  fs.mkdirSync(join(homedir(), ".neurolink"), { recursive: true });
177
- await writeFileAtomic(getCodexSnapshotPath(), JSON.stringify({ originalProviderLine }, null, 2), 0o600);
182
+ await writeFileAtomic(getCodexSnapshotPath(), JSON.stringify({ originalProviderLine: userProviderLine }, null, 2), 0o600);
178
183
  }
179
184
  // Every proxy start applies. Rewriting a file that already matches only
180
185
  // widens the window for racing Codex's own saves.
@@ -937,6 +937,7 @@ export class BaseProvider {
937
937
  csvOptions: options.csvOptions,
938
938
  pdfOptions: options.pdfOptions,
939
939
  videoOptions: options.videoOptions,
940
+ officeOptions: options.officeOptions,
940
941
  // Forward abort, tool filtering, and timeout options to prevent
941
942
  // silent bypass when falling back from real streaming to fake streaming
942
943
  abortSignal: options.abortSignal,
@@ -109,6 +109,11 @@ export class MessageBuilder {
109
109
  // transcription flag are read downstream by the detector, and
110
110
  // a reconstruction that omits them silently restores defaults.
111
111
  videoOptions: options.videoOptions,
112
+ // This object is rebuilt field by field, so anything not named
113
+ // here never reaches the multimodal builder — the same way
114
+ // audioFiles/videoFiles went missing in #284. Office options
115
+ // steer XLSX sheet selection and rendering.
116
+ officeOptions: options.officeOptions,
112
117
  provider: options.provider,
113
118
  model: options.model,
114
119
  temperature: options.temperature,
@@ -244,6 +249,11 @@ export class MessageBuilder {
244
249
  // transcription flag are read downstream by the detector, and
245
250
  // a reconstruction that omits them silently restores defaults.
246
251
  videoOptions: options.videoOptions,
252
+ // This object is rebuilt field by field, so anything not named
253
+ // here never reaches the multimodal builder — the same way
254
+ // audioFiles/videoFiles went missing in #284. Office options
255
+ // steer XLSX sheet selection and rendering.
256
+ officeOptions: options.officeOptions,
247
257
  provider: options.provider,
248
258
  model: options.model,
249
259
  temperature: options.temperature,
package/dist/neurolink.js CHANGED
@@ -4284,6 +4284,7 @@ Current user's request: ${currentInput}`;
4284
4284
  // caller's video settings have ever reached a provider -- frames and
4285
4285
  // quality included, not just the native-video flags added alongside.
4286
4286
  videoOptions: options.videoOptions,
4287
+ officeOptions: options.officeOptions,
4287
4288
  region: options.region,
4288
4289
  tts: options.tts,
4289
4290
  stt: options.stt,
@@ -36,7 +36,7 @@
36
36
  * ```
37
37
  */
38
38
  import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
39
- import type { FileInfo, ProcessOptions, ProcessedExcel, ProcessorFileProcessingResult } from "../../types/index.js";
39
+ import type { ExcelWorksheet, FileInfo, OfficeProcessorOptions, ProcessOptions, ProcessedExcel, ProcessorFileProcessingResult } from "../../types/index.js";
40
40
  /**
41
41
  * Excel Processor - handles .xlsx and .xls files.
42
42
  * Uses exceljs library for parsing with support for large files.
@@ -136,6 +136,18 @@ export declare class ExcelProcessor extends BaseFileProcessor<ProcessedExcel> {
136
136
  */
137
137
  extractSheetRange(buffer: Buffer, sheet?: string | number, rowStart?: number, rowEnd?: number, columns?: string[]): Promise<string>;
138
138
  }
139
+ /**
140
+ * Render processed worksheets into the text handed to the model.
141
+ *
142
+ * @param worksheets - Worksheets extracted from the workbook
143
+ * @param totalRows - Total row count across all extracted worksheets
144
+ * @param options - Office options; `sheetName` selects a single sheet and
145
+ * `formatStyle` chooses the rendering (default `raw`, the tab-separated
146
+ * preview that predates the option)
147
+ * @returns Prompt-ready text, or a message naming the available sheets when
148
+ * `sheetName` matches none of them
149
+ */
150
+ export declare function formatWorksheetsForPrompt(worksheets: ExcelWorksheet[], totalRows: number, options?: OfficeProcessorOptions): string;
139
151
  /**
140
152
  * Singleton Excel processor instance.
141
153
  * Use this for standard Excel processing operations.
@@ -39,6 +39,7 @@ import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
39
39
  import { SIZE_LIMITS } from "../config/index.js";
40
40
  import { FileErrorCode } from "../errors/index.js";
41
41
  import { tryImport } from "../../utils/tryImport.js";
42
+ import { dedupeColumnNames } from "../../utils/csvProcessor.js";
42
43
  let _exceljs = null;
43
44
  async function loadExcelJS() {
44
45
  if (_exceljs) {
@@ -482,6 +483,123 @@ export class ExcelProcessor extends BaseFileProcessor {
482
483
  }
483
484
  }
484
485
  // =============================================================================
486
+ // PROMPT FORMATTING
487
+ // =============================================================================
488
+ /** Rows beyond this per sheet are summarised rather than listed. */
489
+ const PROMPT_PREVIEW_ROWS = 20;
490
+ /** RFC 4180: quote a field that contains a comma, quote or newline. */
491
+ function csvEscape(value) {
492
+ return /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
493
+ }
494
+ /** A pipe inside a cell would otherwise split it into two columns. */
495
+ function markdownEscape(value) {
496
+ return value
497
+ .replace(/\\/g, "\\\\")
498
+ .replace(/\|/g, "\\|")
499
+ .replace(/\r?\n/g, " ");
500
+ }
501
+ function cellText(value) {
502
+ return String(value ?? "");
503
+ }
504
+ function formatSheetRows(sheet, formatStyle) {
505
+ const previewRows = sheet.rows.slice(0, PROMPT_PREVIEW_ROWS);
506
+ if (previewRows.length === 0) {
507
+ return "";
508
+ }
509
+ switch (formatStyle) {
510
+ case "markdown": {
511
+ // `rows[0]` IS the header row (that is where `headers` came from), so
512
+ // the body always starts at index 1 — emitting row 0 again would
513
+ // duplicate the header underneath the separator.
514
+ const header = sheet.headers.length > 0 ? sheet.headers : previewRows[0].map(cellText);
515
+ const body = previewRows.slice(1);
516
+ const columnCount = Math.max(header.length, ...body.map((row) => row.length), 1);
517
+ const pad = (cells) => Array.from({ length: columnCount }, (_, i) => markdownEscape(cells[i] ?? ""));
518
+ return [
519
+ `| ${pad(header).join(" | ")} |`,
520
+ `| ${Array.from({ length: columnCount }, () => "---").join(" | ")} |`,
521
+ ...body.map((row) => `| ${pad(row.map(cellText)).join(" | ")} |`),
522
+ ].join("\n");
523
+ }
524
+ case "csv":
525
+ return previewRows
526
+ .map((row) => row.map((cell) => csvEscape(cellText(cell))).join(","))
527
+ .join("\n");
528
+ case "json": {
529
+ const rawHeader = sheet.headers.length > 0 ? sheet.headers : previewRows[0].map(cellText);
530
+ const body = previewRows.slice(1);
531
+ // A data row's own populated-cell count can exceed the header's — row
532
+ // extraction sizes each row on its own last populated cell, not a
533
+ // workbook-wide column count, so a header truncated by a merged cell
534
+ // (or a value landing in a column the header left blank) is a normal
535
+ // spreadsheet shape. "markdown" already sizes on the widest row for
536
+ // exactly this reason; do the same here instead of zipping strictly to
537
+ // header.length, which silently dropped every cell past it.
538
+ const columnCount = Math.max(rawHeader.length, ...body.map((row) => row.length), 1);
539
+ const extendedHeader = Array.from({ length: columnCount }, (_, i) => rawHeader[i] ?? `Column ${i + 1}`);
540
+ // Object.fromEntries silently drops a column when two header cells are
541
+ // literally the same string (a copy-pasted header, a merged-and-split
542
+ // sheet) — the later column's value overwrites the earlier one under
543
+ // the shared key. Dedupe first so every column keeps its own key.
544
+ const header = dedupeColumnNames(extendedHeader);
545
+ const objects = body.map((row) => Object.fromEntries(header.map((key, i) => [key, row[i] ?? null])));
546
+ return JSON.stringify(objects, null, 2);
547
+ }
548
+ case "raw":
549
+ default:
550
+ // Bug-for-bug with the pre-option behaviour: the header line is printed
551
+ // and then `rows` is printed in full, so row 1 appears twice. Callers
552
+ // that want a clean table ask for "markdown" or "csv".
553
+ return [
554
+ sheet.headers.join("\t"),
555
+ ...previewRows.map((row) => row.map(cellText).join("\t")),
556
+ ].join("\n");
557
+ }
558
+ }
559
+ /**
560
+ * Render processed worksheets into the text handed to the model.
561
+ *
562
+ * @param worksheets - Worksheets extracted from the workbook
563
+ * @param totalRows - Total row count across all extracted worksheets
564
+ * @param options - Office options; `sheetName` selects a single sheet and
565
+ * `formatStyle` chooses the rendering (default `raw`, the tab-separated
566
+ * preview that predates the option)
567
+ * @returns Prompt-ready text, or a message naming the available sheets when
568
+ * `sheetName` matches none of them
569
+ */
570
+ export function formatWorksheetsForPrompt(worksheets, totalRows, options) {
571
+ const requested = options?.sheetName;
572
+ let selected = worksheets;
573
+ if (requested !== undefined) {
574
+ selected = worksheets.filter((sheet) => sheet.name === requested);
575
+ if (selected.length === 0) {
576
+ const available = worksheets.map((sheet) => sheet.name).join(", ");
577
+ return `Sheet "${requested}" not found. Available sheets: ${available || "(none)"}`;
578
+ }
579
+ }
580
+ const formatStyle = options?.formatStyle ?? "raw";
581
+ const selectedRows = selected.reduce((sum, sheet) => sum + sheet.rowCount, 0);
582
+ const scope = requested !== undefined
583
+ ? `Spreadsheet: sheet "${requested}" of ${worksheets.length}, ${selectedRows} rows`
584
+ : `Spreadsheet: ${worksheets.length} sheet(s), ${totalRows} total rows`;
585
+ let text = `${scope}\n`;
586
+ for (const sheet of selected) {
587
+ text += `\n### Sheet: ${sheet.name}\n`;
588
+ text += `Columns (${sheet.columnCount}): ${sheet.headers.join(", ")}\n`;
589
+ text += `Rows: ${sheet.rowCount}\n`;
590
+ const rendered = formatSheetRows(sheet, formatStyle);
591
+ if (!rendered) {
592
+ continue;
593
+ }
594
+ text += `\nData:\n${rendered}\n`;
595
+ const remaining = sheet.rowCount - PROMPT_PREVIEW_ROWS;
596
+ if (remaining > 0) {
597
+ text += `... (${remaining} more rows)\n`;
598
+ }
599
+ }
600
+ return text;
601
+ }
602
+ // =============================================================================
485
603
  // SINGLETON INSTANCE
486
604
  // =============================================================================
487
605
  /**
@@ -35,6 +35,7 @@ import { BaseFileProcessor } from "../base/BaseFileProcessor.js";
35
35
  import { SIZE_LIMITS } from "../config/index.js";
36
36
  import { FileErrorCode } from "../errors/index.js";
37
37
  import { tryImport } from "../../utils/tryImport.js";
38
+ import { htmlToMarkdown } from "../../utils/htmlToMarkdown.js";
38
39
  let _mammoth = null;
39
40
  async function loadMammoth() {
40
41
  if (_mammoth) {
@@ -64,6 +65,22 @@ const SUPPORTED_WORD_EXTENSIONS = [".docx", ".doc"];
64
65
  * Word documents can be larger due to embedded images and complex formatting
65
66
  */
66
67
  const WORD_TIMEOUT_MS = 60000;
68
+ /**
69
+ * Derive the document metrics from the extracted plain text.
70
+ *
71
+ * Counted off `textContent` rather than the markdown so the numbers describe
72
+ * the document's prose, not the syntax added to represent it.
73
+ */
74
+ function measureText(textContent) {
75
+ const trimmed = textContent.trim();
76
+ return {
77
+ wordCount: trimmed ? trimmed.split(/\s+/).length : 0,
78
+ paragraphCount: textContent
79
+ .split(/\n+/)
80
+ .filter((line) => line.trim().length > 0).length,
81
+ characterCount: textContent.length,
82
+ };
83
+ }
67
84
  // =============================================================================
68
85
  // WORD PROCESSOR CLASS
69
86
  // =============================================================================
@@ -144,7 +161,9 @@ export class WordProcessor extends BaseFileProcessor {
144
161
  return {
145
162
  textContent: "",
146
163
  htmlContent: "",
164
+ markdownContent: "",
147
165
  warnings: [],
166
+ ...measureText(""),
148
167
  buffer,
149
168
  mimetype: fileInfo.mimetype ||
150
169
  "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
@@ -267,7 +286,9 @@ export class WordProcessor extends BaseFileProcessor {
267
286
  filename: this.getFilename(fileInfo),
268
287
  textContent,
269
288
  htmlContent,
289
+ markdownContent: htmlToMarkdown(htmlContent),
270
290
  warnings,
291
+ ...measureText(textContent),
271
292
  },
272
293
  };
273
294
  }
@@ -19,8 +19,8 @@ function record(value) {
19
19
  * (plain, carrying a complete tool pair, repeated, or opening with a signed
20
20
  * `thinking` block). A `role: "system"` message is not — the Messages API
21
21
  * requires the initial system prompt in the top-level `system` parameter.
22
- * Items with no role (Codex `function_call` / `function_call_output`) are not
23
- * turns and are left alone.
22
+ * Items with no role (Codex `*_call` / `*_call_output`) are not turns and are
23
+ * left alone.
24
24
  */
25
25
  function isDirectiveTurn(item) {
26
26
  return record(item) && item.role === "system";
@@ -28,13 +28,26 @@ function isDirectiveTurn(item) {
28
28
  function blocksOf(item) {
29
29
  return Array.isArray(item.content) ? item.content : [item];
30
30
  }
31
- /** Tool-call ids this item opens. */
31
+ /**
32
+ * Tool-call ids this item opens.
33
+ *
34
+ * Matched on the `_call` suffix rather than an enumerated list of item types.
35
+ * The Responses API has several call families — `function_call`,
36
+ * `custom_tool_call`, `local_shell_call`, `computer_call` — and an unrecognised
37
+ * one is worse than useless here: it opens no id, so unit grouping cannot see
38
+ * the pair and truncation is free to cut between a call and its answer. A live
39
+ * Codex session failed exactly that way, upstream rejecting the request with
40
+ * "No tool call found for custom tool call output with call_id ...". Suffix
41
+ * matching keeps the next family from reintroducing it silently.
42
+ */
32
43
  function definedCallIds(item) {
33
44
  if (!record(item)) {
34
45
  return [];
35
46
  }
36
47
  const ids = [];
37
- if (item.type === "function_call" && typeof item.call_id === "string") {
48
+ if (typeof item.type === "string" &&
49
+ item.type.endsWith("_call") &&
50
+ typeof item.call_id === "string") {
38
51
  ids.push(item.call_id);
39
52
  }
40
53
  for (const block of blocksOf(item)) {
@@ -46,13 +59,20 @@ function definedCallIds(item) {
46
59
  }
47
60
  return ids;
48
61
  }
49
- /** Tool-call ids this item answers. */
62
+ /**
63
+ * Tool-call ids this item answers.
64
+ *
65
+ * Suffix-matched for the same reason `definedCallIds` is. `_call_output` is
66
+ * checked rather than `_call` because no output type ends in `_call`, so the
67
+ * two predicates stay disjoint without ordering between them.
68
+ */
50
69
  function referencedCallIds(item) {
51
70
  if (!record(item)) {
52
71
  return [];
53
72
  }
54
73
  const ids = [];
55
- if (item.type === "function_call_output" &&
74
+ if (typeof item.type === "string" &&
75
+ item.type.endsWith("_call_output") &&
56
76
  typeof item.call_id === "string") {
57
77
  ids.push(item.call_id);
58
78
  }
@@ -69,7 +89,8 @@ function referencedCallIds(item) {
69
89
  * Group items so a tool call and every answer to it stay in one unit.
70
90
  *
71
91
  * A unit stays open while any call it opened is still unanswered, so removing
72
- * whole units can never strand a `tool_result` or a `function_call_output`.
92
+ * whole units can never strand a tool answer — an Anthropic `tool_result` or
93
+ * any Responses API `*_call_output`.
73
94
  */
74
95
  function groupIntoUnits(items) {
75
96
  const units = [];
@@ -611,6 +611,21 @@ export type OfficeProcessorOptions = {
611
611
  processAllSheets?: boolean;
612
612
  /** For presentations (pptx only): whether to include slide notes */
613
613
  includeSlideNotes?: boolean;
614
+ /**
615
+ * For spreadsheets (xlsx only): restrict processing to the named sheet.
616
+ * When the workbook has no sheet by that name the result says so and lists
617
+ * the names it does have, rather than silently falling back to every sheet.
618
+ * Omit to process every sheet (the default).
619
+ */
620
+ sheetName?: string;
621
+ /**
622
+ * For spreadsheets (xlsx only): how sheet data is rendered into the prompt.
623
+ * - `raw` (default): tab-separated preview — the long-standing behaviour
624
+ * - `csv`: comma-separated values with RFC 4180 quoting
625
+ * - `markdown`: a markdown table per sheet
626
+ * - `json`: an array of row objects keyed by the header row
627
+ */
628
+ formatStyle?: "raw" | "markdown" | "json" | "csv";
614
629
  };
615
630
  /**
616
631
  * File detector options
@@ -18,7 +18,7 @@ import type { AvatarOptions, AvatarResult } from "./avatar.js";
18
18
  import type { MusicOptions, MusicResult } from "./music.js";
19
19
  import type { StandardRecord, ValidationSchema, ZodUnknownSchema } from "./aliases.js";
20
20
  import type { NeurolinkCredentials } from "./providers.js";
21
- import type { CSVProcessorOptions, VideoProcessorOptions, FileWithMetadata, MultimodalAudioEntry, MultimodalVideoEntry, VisionImageOutputFormat } from "./file.js";
21
+ import type { CSVProcessorOptions, VideoProcessorOptions, OfficeProcessorOptions, FileWithMetadata, MultimodalAudioEntry, MultimodalVideoEntry, VisionImageOutputFormat } from "./file.js";
22
22
  import type { WorkflowConfig } from "./workflow.js";
23
23
  import type { Schema, Tool, ToolChoice } from "./tools.js";
24
24
  import type { StepResult, LanguageModel } from "./providers.js";
@@ -150,6 +150,11 @@ export type GenerateOptions = {
150
150
  music?: MusicOptions;
151
151
  };
152
152
  csvOptions?: CSVProcessorOptions;
153
+ /**
154
+ * Office document processing options. Currently consumed by the XLSX path
155
+ * (`sheetName`, `formatStyle`); see OfficeProcessorOptions.
156
+ */
157
+ officeOptions?: OfficeProcessorOptions;
153
158
  /** PDF processing options (#258). */
154
159
  pdfOptions?: {
155
160
  /** Password for an encrypted PDF (image-conversion fallback path). */
@@ -1437,6 +1442,11 @@ export type TextGenerationOptions = {
1437
1442
  * saying so.
1438
1443
  */
1439
1444
  videoOptions?: VideoProcessorOptions;
1445
+ /**
1446
+ * Office document processing options. Currently consumed by the XLSX path
1447
+ * (`sheetName`, `formatStyle`); see OfficeProcessorOptions.
1448
+ */
1449
+ officeOptions?: OfficeProcessorOptions;
1440
1450
  /** PDF processing options (#258). */
1441
1451
  pdfOptions?: {
1442
1452
  /** Password for an encrypted PDF (image-conversion fallback path). */
@@ -639,8 +639,19 @@ export type ProcessedWord = ProcessedFileBase & {
639
639
  textContent: string;
640
640
  /** HTML representation of the Word document */
641
641
  htmlContent: string;
642
+ /**
643
+ * Markdown rendering of `htmlContent`, preserving the structure that plain
644
+ * text loses: headings, ordered/unordered lists and tables.
645
+ */
646
+ markdownContent: string;
642
647
  /** Warnings from mammoth extraction (e.g., unsupported elements) */
643
648
  warnings: string[];
649
+ /** Whitespace-delimited word count of `textContent`. */
650
+ wordCount: number;
651
+ /** Count of non-empty text blocks in `textContent`. */
652
+ paragraphCount: number;
653
+ /** Character count of `textContent`, including whitespace. */
654
+ characterCount: number;
644
655
  };
645
656
  /**
646
657
  * Processed RTF document result.
@@ -16,7 +16,7 @@ import type { AIModelProviderConfig, NeurolinkCredentials } from "./providers.js
16
16
  import type { TTSChunk, TTSOptions, TTSResult } from "./tts.js";
17
17
  import type { STTOptions, STTResult } from "./stt.js";
18
18
  import type { StandardRecord, ValidationSchema } from "./aliases.js";
19
- import type { CSVProcessorOptions, FileWithMetadata, VideoProcessorOptions, VisionImageOutputFormat } from "./file.js";
19
+ import type { CSVProcessorOptions, FileWithMetadata, VideoProcessorOptions, VisionImageOutputFormat, OfficeProcessorOptions } from "./file.js";
20
20
  import type { WorkflowConfig } from "./workflow.js";
21
21
  import type { LanguageModel, StepResult } from "./providers.js";
22
22
  import type { Tool, ToolChoice } from "./tools.js";
@@ -366,6 +366,11 @@ export type StreamOptions = {
366
366
  * saying so.
367
367
  */
368
368
  videoOptions?: VideoProcessorOptions;
369
+ /**
370
+ * Office document processing options. Currently consumed by the XLSX path
371
+ * (`sheetName`, `formatStyle`); see OfficeProcessorOptions.
372
+ */
373
+ officeOptions?: OfficeProcessorOptions;
369
374
  /** PDF processing options (#258). */
370
375
  pdfOptions?: {
371
376
  /** Password for an encrypted PDF (image-conversion fallback path). */
@@ -321,3 +321,22 @@ export type ScalarRecoveryDecision = {
321
321
  } | {
322
322
  kind: "not-json";
323
323
  };
324
+ /**
325
+ * An element in the parsed HTML tree used for Markdown conversion.
326
+ */
327
+ export type MarkdownHtmlElementNode = {
328
+ kind: "element";
329
+ /** Lower-cased tag name. */
330
+ tag: string;
331
+ /** Lower-cased attribute names mapped to their decoded values. */
332
+ attrs: Record<string, string>;
333
+ children: MarkdownHtmlNode[];
334
+ };
335
+ /**
336
+ * A node in the parsed HTML tree used for Markdown conversion: either a text
337
+ * run or an element.
338
+ */
339
+ export type MarkdownHtmlNode = {
340
+ kind: "text";
341
+ value: string;
342
+ } | MarkdownHtmlElementNode;