@deepcitation/deepcitation-js 1.0.5 → 1.0.6

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.
package/README.md CHANGED
@@ -41,7 +41,7 @@ Get a free API key at [deepcitation.com](https://deepcitation.com/signup) — no
41
41
 
42
42
  ```bash
43
43
  # .env
44
- DEEPCITATION_API_KEY=dc_live_your_api_key_here
44
+ DEEPCITATION_API_KEY=sk-dc-your_api_key_here
45
45
  ```
46
46
 
47
47
  ---
@@ -123,7 +123,7 @@ function Response({ citations, verifications }) {
123
123
 
124
124
  ```typescript
125
125
  const dc = new DeepCitation({
126
- apiKey: string, // Your API key (dc_live_* or dc_test_*)
126
+ apiKey: string, // Your API key (sk-dc-*)
127
127
  apiUrl?: string, // Optional: Custom API URL
128
128
  });
129
129
 
@@ -1,5 +1,5 @@
1
1
  import type { Citation } from "../types/index";
2
- import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitationConfig, FileInput, PrepareConvertedFileOptions, PrepareFilesResult, UploadFileOptions, UploadFileResponse, VerifyCitationsFromLlmOutputInput, VerifyCitationsOptions, VerifyCitationsResponse } from "./types";
2
+ import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitationConfig, FileInput, PrepareConvertedFileOptions, PrepareFilesResult, UploadFileOptions, UploadFileResponse, VerifyCitationsFromLlmOutput, VerifyCitationsOptions, VerifyCitationsResponse } from "./types";
3
3
  /**
4
4
  * DeepCitation client for file upload and citation verification.
5
5
  *
@@ -33,6 +33,8 @@ export declare class DeepCitation {
33
33
  * This allows users to reference files by their own IDs
34
34
  */
35
35
  private fileIdMap;
36
+ /** Store file mapping and return public response */
37
+ private storeAndReturnResponse;
36
38
  /**
37
39
  * Create a new DeepCitation client instance.
38
40
  *
@@ -185,7 +187,7 @@ export declare class DeepCitation {
185
187
  * }
186
188
  * ```
187
189
  */
188
- verifyCitationsFromLlmOutput(input: VerifyCitationsFromLlmOutputInput, citations?: {
190
+ verifyCitationsFromLlmOutput(input: VerifyCitationsFromLlmOutput, citations?: {
189
191
  [key: string]: Citation;
190
192
  }): Promise<VerifyCitationsResponse>;
191
193
  /**
@@ -1,6 +1,25 @@
1
1
  import { getAllCitationsFromLlmOutput } from "../parsing/parseCitation";
2
2
  import { generateCitationKey } from "../react/utils";
3
3
  const DEFAULT_API_URL = "https://api.deepcitation.com";
4
+ /** Convert File/Blob/Buffer to a Blob suitable for FormData */
5
+ function toBlob(file, filename) {
6
+ if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
7
+ const uint8 = Uint8Array.from(file);
8
+ return { blob: new Blob([uint8]), name: filename || "document" };
9
+ }
10
+ if (file instanceof Blob) {
11
+ return {
12
+ blob: file,
13
+ name: filename || (file instanceof File ? file.name : "document"),
14
+ };
15
+ }
16
+ throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
17
+ }
18
+ /** Extract error message from API response */
19
+ async function extractErrorMessage(response, fallbackAction) {
20
+ const error = await response.json().catch(() => ({}));
21
+ return error?.error?.message || `${fallbackAction} failed with status ${response.status}`;
22
+ }
4
23
  /**
5
24
  * DeepCitation client for file upload and citation verification.
6
25
  *
@@ -34,6 +53,12 @@ export class DeepCitation {
34
53
  * This allows users to reference files by their own IDs
35
54
  */
36
55
  fileIdMap = new Map();
56
+ /** Store file mapping and return public response */
57
+ storeAndReturnResponse(apiResponse) {
58
+ this.fileIdMap.set(apiResponse.fileId, { attachmentId: apiResponse.attachmentId });
59
+ const { attachmentId: _, ...publicResponse } = apiResponse;
60
+ return publicResponse;
61
+ }
37
62
  /**
38
63
  * Create a new DeepCitation client instance.
39
64
  *
@@ -71,51 +96,22 @@ export class DeepCitation {
71
96
  * ```
72
97
  */
73
98
  async uploadFile(file, options) {
99
+ const { blob, name } = toBlob(file, options?.filename);
74
100
  const formData = new FormData();
75
- // Handle different input types
76
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
77
- // Node.js Buffer - copy to a new ArrayBuffer for Blob compatibility
78
- const filename = options?.filename || "document";
79
- // Use Uint8Array.from to create a copy that's definitely backed by ArrayBuffer (not SharedArrayBuffer)
80
- const uint8 = Uint8Array.from(file);
81
- const blob = new Blob([uint8]);
82
- formData.append("file", blob, filename);
83
- }
84
- else if (file instanceof Blob) {
85
- // File or Blob
86
- const filename = options?.filename || (file instanceof File ? file.name : "document");
87
- formData.append("file", file, filename);
88
- }
89
- else {
90
- throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
91
- }
92
- // Add optional fields
93
- if (options?.fileId) {
101
+ formData.append("file", blob, name);
102
+ if (options?.fileId)
94
103
  formData.append("fileId", options.fileId);
95
- }
96
- if (options?.filename) {
104
+ if (options?.filename)
97
105
  formData.append("filename", options.filename);
98
- }
99
106
  const response = await fetch(`${this.apiUrl}/prepareFile`, {
100
107
  method: "POST",
101
- headers: {
102
- Authorization: `Bearer ${this.apiKey}`,
103
- },
108
+ headers: { Authorization: `Bearer ${this.apiKey}` },
104
109
  body: formData,
105
110
  });
106
111
  if (!response.ok) {
107
- const error = await response.json().catch(() => ({}));
108
- throw new Error(error?.error?.message || `Upload failed with status ${response.status}`);
112
+ throw new Error(await extractErrorMessage(response, "Upload"));
109
113
  }
110
- // Internal response includes attachmentId which we need for verification
111
- const apiResponse = (await response.json());
112
- // Store the mapping for later verification calls
113
- this.fileIdMap.set(apiResponse.fileId, {
114
- attachmentId: apiResponse.attachmentId,
115
- });
116
- // Return public response without internal fields
117
- const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
118
- return publicResponse;
114
+ return this.storeAndReturnResponse(await response.json());
119
115
  }
120
116
  /**
121
117
  * Convert a URL or Office file to PDF for citation verification.
@@ -150,75 +146,42 @@ export class DeepCitation {
150
146
  * ```
151
147
  */
152
148
  async convertToPdf(input) {
153
- // Handle string URL shorthand
154
149
  const inputObj = typeof input === "string" ? { url: input } : input;
155
- const { url, file, filename, fileId, singlePage } = inputObj;
150
+ const { url, file, filename, fileId } = inputObj;
156
151
  if (!url && !file) {
157
152
  throw new Error("Either url or file must be provided");
158
153
  }
159
154
  let response;
160
155
  if (url) {
161
- // URL conversion - send as JSON
162
156
  response = await fetch(`${this.apiUrl}/convertFile`, {
163
157
  method: "POST",
164
158
  headers: {
165
159
  Authorization: `Bearer ${this.apiKey}`,
166
160
  "Content-Type": "application/json",
167
161
  },
168
- body: JSON.stringify({
169
- url,
170
- filename,
171
- fileId,
172
- singlePage,
173
- }),
162
+ body: JSON.stringify({ url, filename, fileId }),
174
163
  });
175
164
  }
176
- else if (file) {
177
- // Office file conversion - send as multipart
165
+ else {
166
+ const { blob, name } = toBlob(file, filename);
178
167
  const formData = new FormData();
179
- if (typeof Buffer !== "undefined" && Buffer.isBuffer(file)) {
180
- const fname = filename || "document";
181
- const uint8 = Uint8Array.from(file);
182
- const blob = new Blob([uint8]);
183
- formData.append("file", blob, fname);
184
- }
185
- else if (file instanceof Blob) {
186
- const fname = filename || (file instanceof File ? file.name : "document");
187
- formData.append("file", file, fname);
188
- }
189
- else {
190
- throw new Error("Invalid file type. Expected File, Blob, or Buffer.");
191
- }
192
- if (fileId) {
168
+ formData.append("file", blob, name);
169
+ if (fileId)
193
170
  formData.append("fileId", fileId);
194
- }
195
- if (filename) {
171
+ if (filename)
196
172
  formData.append("filename", filename);
197
- }
198
173
  response = await fetch(`${this.apiUrl}/convertFile`, {
199
174
  method: "POST",
200
- headers: {
201
- Authorization: `Bearer ${this.apiKey}`,
202
- },
175
+ headers: { Authorization: `Bearer ${this.apiKey}` },
203
176
  body: formData,
204
177
  });
205
178
  }
206
- else {
207
- throw new Error("Either url or file must be provided");
208
- }
209
179
  if (!response.ok) {
210
- const error = await response.json().catch(() => ({}));
211
- throw new Error(error?.error?.message ||
212
- `Conversion failed with status ${response.status}`);
180
+ throw new Error(await extractErrorMessage(response, "Conversion"));
213
181
  }
214
- // Internal response includes attachmentId which we need for the two-step flow
215
182
  const apiResponse = (await response.json());
216
- // Store the mapping for later verification and prepareConvertedFile calls
217
- this.fileIdMap.set(apiResponse.fileId, {
218
- attachmentId: apiResponse.attachmentId,
219
- });
220
- // Return public response without internal fields
221
- const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
183
+ this.fileIdMap.set(apiResponse.fileId, { attachmentId: apiResponse.attachmentId });
184
+ const { attachmentId: _, ...publicResponse } = apiResponse;
222
185
  return publicResponse;
223
186
  }
224
187
  /**
@@ -242,7 +205,6 @@ export class DeepCitation {
242
205
  * ```
243
206
  */
244
207
  async prepareConvertedFile(options) {
245
- // Look up the internal attachmentId from the fileId
246
208
  const fileInfo = this.fileIdMap.get(options.fileId);
247
209
  if (!fileInfo) {
248
210
  throw new Error(`File ID "${options.fileId}" not found. Make sure to call convertToPdf() first.`);
@@ -259,18 +221,9 @@ export class DeepCitation {
259
221
  }),
260
222
  });
261
223
  if (!response.ok) {
262
- const error = await response.json().catch(() => ({}));
263
- throw new Error(error?.error?.message || `Prepare failed with status ${response.status}`);
224
+ throw new Error(await extractErrorMessage(response, "Prepare"));
264
225
  }
265
- // Internal response includes attachmentId
266
- const apiResponse = (await response.json());
267
- // Update the mapping (attachmentId should remain the same)
268
- this.fileIdMap.set(apiResponse.fileId, {
269
- attachmentId: apiResponse.attachmentId,
270
- });
271
- // Return public response without internal fields
272
- const { attachmentId: _attachmentId, ...publicResponse } = apiResponse;
273
- return publicResponse;
226
+ return this.storeAndReturnResponse(await response.json());
274
227
  }
275
228
  /**
276
229
  * Upload multiple files for citation verification and get structured content.
@@ -377,9 +330,7 @@ export class DeepCitation {
377
330
  }),
378
331
  });
379
332
  if (!response.ok) {
380
- const error = await response.json().catch(() => ({}));
381
- throw new Error(error?.error?.message ||
382
- `Verification failed with status ${response.status}`);
333
+ throw new Error(await extractErrorMessage(response, "Verification"));
383
334
  }
384
335
  return (await response.json());
385
336
  }
@@ -414,7 +365,7 @@ export class DeepCitation {
414
365
  // Note: fileDataParts is now only used to identify which files to verify
415
366
  // The mapping from fileId to attachmentId must be registered via uploadFile() or prepareFiles()
416
367
  // in the same session. For Zero Data Retention scenarios, use verifyCitations() directly.
417
- // Group citations by fileId and verify each group
368
+ // Group citations by fileId
418
369
  const citationsByFile = new Map();
419
370
  for (const [key, citation] of Object.entries(citations)) {
420
371
  const fileId = citation.fileId || "";
@@ -423,35 +374,16 @@ export class DeepCitation {
423
374
  }
424
375
  citationsByFile.get(fileId)[key] = citation;
425
376
  }
426
- // Verify citations for each file
427
- const allHighlights = {};
377
+ // Filter to only registered files and verify in parallel
378
+ const verificationPromises = [];
428
379
  for (const [fileId, fileCitations] of citationsByFile) {
429
- // Check if we have the file registered
430
- const fileInfo = this.fileIdMap.get(fileId);
431
- if (!fileInfo) {
432
- // Skip citations for unregistered files
433
- continue;
380
+ if (this.fileIdMap.has(fileId)) {
381
+ verificationPromises.push(this.verifyCitations(fileId, fileCitations, { outputImageFormat }));
434
382
  }
435
- const response = await fetch(`${this.apiUrl}/verifyCitation`, {
436
- method: "POST",
437
- headers: {
438
- Authorization: `Bearer ${this.apiKey}`,
439
- "Content-Type": "application/json",
440
- },
441
- body: JSON.stringify({
442
- data: {
443
- attachmentId: fileInfo.attachmentId,
444
- citations: fileCitations,
445
- outputImageFormat,
446
- },
447
- }),
448
- });
449
- if (!response.ok) {
450
- const error = await response.json().catch(() => ({}));
451
- throw new Error(error?.error?.message ||
452
- `Verification failed with status ${response.status}`);
453
- }
454
- const result = (await response.json());
383
+ }
384
+ const results = await Promise.all(verificationPromises);
385
+ const allHighlights = {};
386
+ for (const result of results) {
455
387
  Object.assign(allHighlights, result.foundHighlights);
456
388
  }
457
389
  return { foundHighlights: allHighlights };
@@ -1,2 +1,2 @@
1
1
  export { DeepCitation } from "./DeepCitation";
2
- export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutputInput, ConvertFileInput, ConvertFileResponse, PrepareConvertedFileOptions, } from "./types";
2
+ export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutput, ConvertFileInput, ConvertFileResponse, PrepareConvertedFileOptions, } from "./types";
@@ -3,7 +3,7 @@ import type { Citation, FoundHighlightLocation } from "../types/index";
3
3
  * Configuration options for the DeepCitation client
4
4
  */
5
5
  export interface DeepCitationConfig {
6
- /** Your DeepCitation API key (starts with dc_live_ or dc_test_) */
6
+ /** Your DeepCitation API key (starts with sk-dc-) */
7
7
  apiKey: string;
8
8
  /** Optional custom API base URL. Defaults to https://api.deepcitation.com */
9
9
  apiUrl?: string;
@@ -94,7 +94,7 @@ export interface PrepareFilesResult {
94
94
  /**
95
95
  * Input for verifyCitationsFromLlmOutput
96
96
  */
97
- export interface VerifyCitationsFromLlmOutputInput {
97
+ export interface VerifyCitationsFromLlmOutput {
98
98
  /** The LLM response containing citations */
99
99
  llmOutput: string;
100
100
  /** Optional file references (required for Zero Data Retention or after storage expires) */
@@ -114,8 +114,6 @@ export interface ConvertFileInput {
114
114
  filename?: string;
115
115
  /** Optional custom file ID */
116
116
  fileId?: string;
117
- /** For URLs: render as single long page instead of paginated */
118
- singlePage?: boolean;
119
117
  }
120
118
  /**
121
119
  * Response from convertFile
@@ -146,12 +144,3 @@ export interface PrepareConvertedFileOptions {
146
144
  /** The file ID from a previous convertFile call */
147
145
  fileId: string;
148
146
  }
149
- /**
150
- * @deprecated Use PrepareConvertedFileOptions instead
151
- */
152
- export interface PrepareFileFromAttachmentOptions {
153
- /** The attachment ID from a previous convertFile call */
154
- attachmentId: string;
155
- /** Optional custom file ID */
156
- fileId?: string;
157
- }
package/lib/index.d.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  * @packageDocumentation
4
4
  */
5
5
  export { DeepCitation } from "./client/index.js";
6
- export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutputInput, } from "./client/index.js";
6
+ export type { DeepCitationConfig, UploadFileResponse, UploadFileOptions, VerifyCitationsResponse, VerifyCitationsOptions, CitationInput, FileInput, FileDataPart, PrepareFilesResult, VerifyCitationsFromLlmOutput, } from "./client/index.js";
7
7
  export { parseCitation, getCitationStatus, getAllCitationsFromLlmOutput, groupCitationsByFileId, groupCitationsByFileIdObject, } from "./parsing/parseCitation.js";
8
8
  export { normalizeCitations, getCitationPageNumber, } from "./parsing/normalizeCitation.js";
9
9
  export { isGeminiGarbage, cleanRepeatingLastSentence, } from "./parsing/parseWorkAround.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deepcitation/deepcitation-js",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "DeepCitation JavaScript SDK for deterministic AI citation verification",
5
5
  "type": "module",
6
6
  "private": false,