@deepcitation/deepcitation-js 1.0.5 → 1.0.7

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
  *
@@ -28,11 +28,6 @@ import type { CitationInput, ConvertFileInput, ConvertFileResponse, DeepCitation
28
28
  export declare class DeepCitation {
29
29
  private readonly apiKey;
30
30
  private readonly apiUrl;
31
- /**
32
- * Stores mapping of user-provided fileId to internal attachmentId
33
- * This allows users to reference files by their own IDs
34
- */
35
- private fileIdMap;
36
31
  /**
37
32
  * Create a new DeepCitation client instance.
38
33
  *
@@ -185,20 +180,7 @@ export declare class DeepCitation {
185
180
  * }
186
181
  * ```
187
182
  */
188
- verifyCitationsFromLlmOutput(input: VerifyCitationsFromLlmOutputInput, citations?: {
183
+ verifyCitationsFromLlmOutput(input: VerifyCitationsFromLlmOutput, citations?: {
189
184
  [key: string]: Citation;
190
185
  }): Promise<VerifyCitationsResponse>;
191
- /**
192
- * Register a file that was uploaded separately (e.g., via direct API call).
193
- * This allows you to use verifyCitations with files not uploaded via uploadFile().
194
- *
195
- * @param fileId - Your file ID
196
- * @param attachmentId - The internal attachment ID
197
- */
198
- registerFile(fileId: string, attachmentId: string): void;
199
- /**
200
- * Clear the internal file ID mapping.
201
- * Useful for cleanup or when working with many files.
202
- */
203
- clearFileMap(): void;
204
186
  }
@@ -1,6 +1,26 @@
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 ||
22
+ `${fallbackAction} failed with status ${response.status}`);
23
+ }
4
24
  /**
5
25
  * DeepCitation client for file upload and citation verification.
6
26
  *
@@ -29,11 +49,6 @@ const DEFAULT_API_URL = "https://api.deepcitation.com";
29
49
  export class DeepCitation {
30
50
  apiKey;
31
51
  apiUrl;
32
- /**
33
- * Stores mapping of user-provided fileId to internal attachmentId
34
- * This allows users to reference files by their own IDs
35
- */
36
- fileIdMap = new Map();
37
52
  /**
38
53
  * Create a new DeepCitation client instance.
39
54
  *
@@ -71,51 +86,22 @@ export class DeepCitation {
71
86
  * ```
72
87
  */
73
88
  async uploadFile(file, options) {
89
+ const { blob, name } = toBlob(file, options?.filename);
74
90
  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) {
91
+ formData.append("file", blob, name);
92
+ if (options?.fileId)
94
93
  formData.append("fileId", options.fileId);
95
- }
96
- if (options?.filename) {
94
+ if (options?.filename)
97
95
  formData.append("filename", options.filename);
98
- }
99
96
  const response = await fetch(`${this.apiUrl}/prepareFile`, {
100
97
  method: "POST",
101
- headers: {
102
- Authorization: `Bearer ${this.apiKey}`,
103
- },
98
+ headers: { Authorization: `Bearer ${this.apiKey}` },
104
99
  body: formData,
105
100
  });
106
101
  if (!response.ok) {
107
- const error = await response.json().catch(() => ({}));
108
- throw new Error(error?.error?.message || `Upload failed with status ${response.status}`);
102
+ throw new Error(await extractErrorMessage(response, "Upload"));
109
103
  }
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;
104
+ return (await response.json());
119
105
  }
120
106
  /**
121
107
  * Convert a URL or Office file to PDF for citation verification.
@@ -150,76 +136,40 @@ export class DeepCitation {
150
136
  * ```
151
137
  */
152
138
  async convertToPdf(input) {
153
- // Handle string URL shorthand
154
139
  const inputObj = typeof input === "string" ? { url: input } : input;
155
- const { url, file, filename, fileId, singlePage } = inputObj;
140
+ const { url, file, filename, fileId } = inputObj;
156
141
  if (!url && !file) {
157
142
  throw new Error("Either url or file must be provided");
158
143
  }
159
144
  let response;
160
145
  if (url) {
161
- // URL conversion - send as JSON
162
146
  response = await fetch(`${this.apiUrl}/convertFile`, {
163
147
  method: "POST",
164
148
  headers: {
165
149
  Authorization: `Bearer ${this.apiKey}`,
166
150
  "Content-Type": "application/json",
167
151
  },
168
- body: JSON.stringify({
169
- url,
170
- filename,
171
- fileId,
172
- singlePage,
173
- }),
152
+ body: JSON.stringify({ url, filename, fileId }),
174
153
  });
175
154
  }
176
- else if (file) {
177
- // Office file conversion - send as multipart
155
+ else {
156
+ const { blob, name } = toBlob(file, filename);
178
157
  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) {
158
+ formData.append("file", blob, name);
159
+ if (fileId)
193
160
  formData.append("fileId", fileId);
194
- }
195
- if (filename) {
161
+ if (filename)
196
162
  formData.append("filename", filename);
197
- }
198
163
  response = await fetch(`${this.apiUrl}/convertFile`, {
199
164
  method: "POST",
200
- headers: {
201
- Authorization: `Bearer ${this.apiKey}`,
202
- },
165
+ headers: { Authorization: `Bearer ${this.apiKey}` },
203
166
  body: formData,
204
167
  });
205
168
  }
206
- else {
207
- throw new Error("Either url or file must be provided");
208
- }
209
169
  if (!response.ok) {
210
- const error = await response.json().catch(() => ({}));
211
- throw new Error(error?.error?.message ||
212
- `Conversion failed with status ${response.status}`);
170
+ throw new Error(await extractErrorMessage(response, "Conversion"));
213
171
  }
214
- // Internal response includes attachmentId which we need for the two-step flow
215
- 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;
222
- return publicResponse;
172
+ return (await response.json());
223
173
  }
224
174
  /**
225
175
  * Prepare a previously converted file for citation verification.
@@ -242,11 +192,6 @@ export class DeepCitation {
242
192
  * ```
243
193
  */
244
194
  async prepareConvertedFile(options) {
245
- // Look up the internal attachmentId from the fileId
246
- const fileInfo = this.fileIdMap.get(options.fileId);
247
- if (!fileInfo) {
248
- throw new Error(`File ID "${options.fileId}" not found. Make sure to call convertToPdf() first.`);
249
- }
250
195
  const response = await fetch(`${this.apiUrl}/prepareFile`, {
251
196
  method: "POST",
252
197
  headers: {
@@ -254,23 +199,13 @@ export class DeepCitation {
254
199
  "Content-Type": "application/json",
255
200
  },
256
201
  body: JSON.stringify({
257
- attachmentId: fileInfo.attachmentId,
258
202
  fileId: options.fileId,
259
203
  }),
260
204
  });
261
205
  if (!response.ok) {
262
- const error = await response.json().catch(() => ({}));
263
- throw new Error(error?.error?.message || `Prepare failed with status ${response.status}`);
206
+ throw new Error(await extractErrorMessage(response, "Prepare"));
264
207
  }
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;
208
+ return (await response.json());
274
209
  }
275
210
  /**
276
211
  * Upload multiple files for citation verification and get structured content.
@@ -333,11 +268,6 @@ export class DeepCitation {
333
268
  * ```
334
269
  */
335
270
  async verifyCitations(fileId, citations, options) {
336
- // Look up the internal IDs from our map
337
- const fileInfo = this.fileIdMap.get(fileId);
338
- if (!fileInfo) {
339
- throw new Error(`File ID "${fileId}" not found. Make sure to upload the file first with uploadFile().`);
340
- }
341
271
  // Normalize citations to a map with citation keys
342
272
  const citationMap = {};
343
273
  if (Array.isArray(citations)) {
@@ -362,26 +292,27 @@ export class DeepCitation {
362
292
  else {
363
293
  throw new Error("Invalid citations format");
364
294
  }
365
- const response = await fetch(`${this.apiUrl}/verifyCitation`, {
295
+ const requestUrl = `${this.apiUrl}/verifyCitations`;
296
+ const requestBody = {
297
+ data: {
298
+ fileId,
299
+ citations: citationMap,
300
+ outputImageFormat: options?.outputImageFormat || "avif",
301
+ },
302
+ };
303
+ const response = await fetch(requestUrl, {
366
304
  method: "POST",
367
305
  headers: {
368
306
  Authorization: `Bearer ${this.apiKey}`,
369
307
  "Content-Type": "application/json",
370
308
  },
371
- body: JSON.stringify({
372
- data: {
373
- attachmentId: fileInfo.attachmentId,
374
- citations: citationMap,
375
- outputImageFormat: options?.outputImageFormat || "avif",
376
- },
377
- }),
309
+ body: JSON.stringify(requestBody),
378
310
  });
379
311
  if (!response.ok) {
380
- const error = await response.json().catch(() => ({}));
381
- throw new Error(error?.error?.message ||
382
- `Verification failed with status ${response.status}`);
312
+ throw new Error(await extractErrorMessage(response, "Verification"));
383
313
  }
384
- return (await response.json());
314
+ const result = (await response.json());
315
+ return result;
385
316
  }
386
317
  /**
387
318
  * Verify citations from LLM output with automatic parsing.
@@ -411,10 +342,7 @@ export class DeepCitation {
411
342
  if (Object.keys(citations).length === 0) {
412
343
  return { foundHighlights: {} };
413
344
  }
414
- // Note: fileDataParts is now only used to identify which files to verify
415
- // The mapping from fileId to attachmentId must be registered via uploadFile() or prepareFiles()
416
- // in the same session. For Zero Data Retention scenarios, use verifyCitations() directly.
417
- // Group citations by fileId and verify each group
345
+ // Group citations by fileId
418
346
  const citationsByFile = new Map();
419
347
  for (const [key, citation] of Object.entries(citations)) {
420
348
  const fileId = citation.fileId || "";
@@ -423,54 +351,18 @@ export class DeepCitation {
423
351
  }
424
352
  citationsByFile.get(fileId)[key] = citation;
425
353
  }
426
- // Verify citations for each file
427
- const allHighlights = {};
354
+ // Verify all files in parallel
355
+ const verificationPromises = [];
428
356
  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;
434
- }
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}`);
357
+ if (fileId) {
358
+ verificationPromises.push(this.verifyCitations(fileId, fileCitations, { outputImageFormat }));
453
359
  }
454
- const result = (await response.json());
360
+ }
361
+ const results = await Promise.all(verificationPromises);
362
+ const allHighlights = {};
363
+ for (const result of results) {
455
364
  Object.assign(allHighlights, result.foundHighlights);
456
365
  }
457
366
  return { foundHighlights: allHighlights };
458
367
  }
459
- /**
460
- * Register a file that was uploaded separately (e.g., via direct API call).
461
- * This allows you to use verifyCitations with files not uploaded via uploadFile().
462
- *
463
- * @param fileId - Your file ID
464
- * @param attachmentId - The internal attachment ID
465
- */
466
- registerFile(fileId, attachmentId) {
467
- this.fileIdMap.set(fileId, { attachmentId });
468
- }
469
- /**
470
- * Clear the internal file ID mapping.
471
- * Useful for cleanup or when working with many files.
472
- */
473
- clearFileMap() {
474
- this.fileIdMap.clear();
475
- }
476
368
  }
@@ -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.7",
4
4
  "description": "DeepCitation JavaScript SDK for deterministic AI citation verification",
5
5
  "type": "module",
6
6
  "private": false,