@docmana/sdk 0.3.4 → 0.4.0
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/CHANGELOG.md +4 -0
- package/README.md +123 -211
- package/dist/api/execution-result.d.ts +3 -0
- package/dist/api/execution-status.d.ts +6 -0
- package/dist/api/run-flow.d.ts +6 -0
- package/dist/cli.mjs +225 -250
- package/dist/cli.mjs.map +1 -1
- package/dist/client.d.ts +21 -0
- package/dist/config.d.ts +21 -0
- package/dist/errors.d.ts +55 -0
- package/dist/files/resolve-input.d.ts +11 -0
- package/dist/http/http-client.d.ts +26 -0
- package/dist/index.d.ts +4 -335
- package/dist/index.js +167 -291
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +167 -291
- package/dist/index.mjs.map +1 -1
- package/dist/models/docmana-api-document-execution-result.model.d.ts +9 -0
- package/dist/models/docmana-api-document-request.model.d.ts +15 -0
- package/dist/models/docmana-api-execution-result.model.d.ts +24 -0
- package/dist/models/docmana-api-node-result.model.d.ts +28 -0
- package/dist/models/docmana-classification-result.model.d.ts +3 -0
- package/dist/models/docmana-conclusion-result.model.d.ts +3 -0
- package/dist/models/docmana-cross-validation-result.model.d.ts +3 -0
- package/dist/models/docmana-document-mapping.model.d.ts +4 -0
- package/dist/models/docmana-document-result.model.d.ts +11 -0
- package/dist/models/docmana-execution-result.model.d.ts +15 -0
- package/dist/models/docmana-extraction-result.model.d.ts +3 -0
- package/dist/models/docmana-metadata-extraction-result.model.d.ts +3 -0
- package/dist/models/docmana-node-result.model.d.ts +7 -0
- package/dist/models/docmana-score-node-result.model.d.ts +5 -0
- package/dist/models/docmana-validation-result.model.d.ts +3 -0
- package/dist/models/document-content.model.d.ts +4 -0
- package/dist/models/execution-progress.model.d.ts +4 -0
- package/dist/models/execution-status.enum.d.ts +1 -0
- package/dist/models/flow-configs.model.d.ts +16 -0
- package/dist/models/flow-edge.model.d.ts +4 -0
- package/dist/models/flow-node-type.enum.d.ts +1 -0
- package/dist/models/flow-node.model.d.ts +33 -0
- package/dist/models/flow.model.d.ts +11 -0
- package/dist/models/index.d.ts +26 -0
- package/dist/models/node-translation-result.model.d.ts +7 -0
- package/dist/models/physical-document.model.d.ts +9 -0
- package/dist/models/virtual-document.model.d.ts +10 -0
- package/dist/polling/poll.d.ts +15 -0
- package/dist/types.d.ts +76 -0
- package/package.json +17 -14
- package/dist/index.d.cts +0 -335
package/dist/cli.mjs
CHANGED
|
@@ -61,7 +61,14 @@ var DocmanaAbortError = class extends DocmanaError {
|
|
|
61
61
|
super(message, { code: "aborted" });
|
|
62
62
|
}
|
|
63
63
|
};
|
|
64
|
-
|
|
64
|
+
var DocmanaRateLimitError = class extends DocmanaError {
|
|
65
|
+
retryAfterMs;
|
|
66
|
+
constructor(message, opts = {}) {
|
|
67
|
+
super(message, { ...opts, code: "rate_limited" });
|
|
68
|
+
this.retryAfterMs = opts.retryAfterMs;
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
function errorFromResponse(status, message, requestId, retryAfterMs) {
|
|
65
72
|
const opts = { status, requestId };
|
|
66
73
|
switch (status) {
|
|
67
74
|
case 400:
|
|
@@ -72,6 +79,8 @@ function errorFromResponse(status, message, requestId) {
|
|
|
72
79
|
return new DocmanaPermissionError(message, opts);
|
|
73
80
|
case 404:
|
|
74
81
|
return new DocmanaNotFoundError(message, opts);
|
|
82
|
+
case 429:
|
|
83
|
+
return new DocmanaRateLimitError(message, { ...opts, retryAfterMs });
|
|
75
84
|
default:
|
|
76
85
|
return new DocmanaError(message, opts);
|
|
77
86
|
}
|
|
@@ -159,29 +168,33 @@ var TokenManager = class {
|
|
|
159
168
|
|
|
160
169
|
// src/config.ts
|
|
161
170
|
var DEFAULTS = {
|
|
162
|
-
apiBaseUrl: "https://api.docmana.ai",
|
|
163
171
|
tokenEndpoint: "https://4fe70f5b-e013-4f65-9fa7-3109a33beba5.ciamlogin.com/4fe70f5b-e013-4f65-9fa7-3109a33beba5/oauth2/v2.0/token",
|
|
164
172
|
scope: "api://d781e6ba-cc08-4618-8099-ad968abd2b9e/.default",
|
|
165
173
|
timeoutMs: 3e5,
|
|
166
|
-
pollIntervalMs: 2e3
|
|
174
|
+
pollIntervalMs: 2e3,
|
|
175
|
+
pollMaxIntervalMs: 1e4
|
|
167
176
|
};
|
|
168
177
|
function resolveConfig(config) {
|
|
169
|
-
if (!config.
|
|
170
|
-
if (!config.
|
|
171
|
-
|
|
178
|
+
if (!config.apiBaseUrl) throw new Error("DocmanaConfig.apiBaseUrl is required");
|
|
179
|
+
if (!config.accessToken && !config.getAccessToken) {
|
|
180
|
+
throw new Error("DocmanaConfig.accessToken or DocmanaConfig.getAccessToken is required");
|
|
181
|
+
}
|
|
182
|
+
const apiBaseUrl = normalizeApiBaseUrl(config.apiBaseUrl);
|
|
172
183
|
return {
|
|
173
|
-
clientId: config.clientId,
|
|
174
|
-
clientSecret: config.clientSecret,
|
|
175
184
|
apiBaseUrl,
|
|
176
|
-
|
|
177
|
-
|
|
185
|
+
accessToken: config.accessToken,
|
|
186
|
+
getAccessToken: config.getAccessToken,
|
|
178
187
|
timeoutMs: config.timeoutMs ?? DEFAULTS.timeoutMs,
|
|
179
188
|
pollIntervalMs: config.pollIntervalMs ?? DEFAULTS.pollIntervalMs,
|
|
189
|
+
pollMaxIntervalMs: config.pollMaxIntervalMs ?? DEFAULTS.pollMaxIntervalMs,
|
|
180
190
|
fetchImpl: config.fetch ?? fetch,
|
|
181
|
-
headers: config.headers ?? {}
|
|
182
|
-
tokenCache: config.tokenCache
|
|
191
|
+
headers: config.headers ?? {}
|
|
183
192
|
};
|
|
184
193
|
}
|
|
194
|
+
function normalizeApiBaseUrl(value) {
|
|
195
|
+
const trimmed = value.replace(/\/+$/, "");
|
|
196
|
+
return /\/v\d+$/i.test(trimmed) ? trimmed : `${trimmed}/v1`;
|
|
197
|
+
}
|
|
185
198
|
|
|
186
199
|
// src/http/http-client.ts
|
|
187
200
|
var HttpClient = class {
|
|
@@ -201,19 +214,23 @@ var HttpClient = class {
|
|
|
201
214
|
}
|
|
202
215
|
async requestRaw(method, path, options = {}) {
|
|
203
216
|
let res = await this.send(method, path, options);
|
|
204
|
-
if (res.status === 401) {
|
|
205
|
-
this.
|
|
206
|
-
res = await this.send(method, path, options);
|
|
217
|
+
if (res.status === 401 && this.opts.getAccessToken) {
|
|
218
|
+
res = await this.send(method, path, options, true);
|
|
207
219
|
}
|
|
208
220
|
if (!res.ok) {
|
|
209
221
|
const requestId = res.headers.get("x-request-id") ?? void 0;
|
|
210
222
|
const message = await this.extractMessage(res);
|
|
211
|
-
throw errorFromResponse(
|
|
223
|
+
throw errorFromResponse(
|
|
224
|
+
res.status,
|
|
225
|
+
message,
|
|
226
|
+
requestId,
|
|
227
|
+
parseRetryAfterMs(res.headers.get("retry-after"))
|
|
228
|
+
);
|
|
212
229
|
}
|
|
213
230
|
return res;
|
|
214
231
|
}
|
|
215
|
-
async send(method, path, options) {
|
|
216
|
-
const token = await this.
|
|
232
|
+
async send(method, path, options, forceRefresh = false) {
|
|
233
|
+
const token = await this.resolveToken(forceRefresh);
|
|
217
234
|
const url = new URL(this.opts.apiBaseUrl + path);
|
|
218
235
|
for (const [k, v] of Object.entries(options.query ?? {})) url.searchParams.set(k, v);
|
|
219
236
|
const headers = {
|
|
@@ -235,6 +252,13 @@ var HttpClient = class {
|
|
|
235
252
|
throw err;
|
|
236
253
|
}
|
|
237
254
|
}
|
|
255
|
+
async resolveToken(forceRefresh) {
|
|
256
|
+
const token = this.opts.getAccessToken ? await this.opts.getAccessToken(forceRefresh ? { forceRefresh: true } : void 0) : this.opts.accessToken;
|
|
257
|
+
if (!token) {
|
|
258
|
+
throw new DocmanaError("Missing Docmana access token", { code: "auth_missing" });
|
|
259
|
+
}
|
|
260
|
+
return token;
|
|
261
|
+
}
|
|
238
262
|
async extractMessage(res) {
|
|
239
263
|
try {
|
|
240
264
|
const data = await res.clone().json();
|
|
@@ -244,6 +268,18 @@ var HttpClient = class {
|
|
|
244
268
|
}
|
|
245
269
|
}
|
|
246
270
|
};
|
|
271
|
+
function parseRetryAfterMs(value) {
|
|
272
|
+
if (!value) return void 0;
|
|
273
|
+
const seconds = Number(value);
|
|
274
|
+
if (Number.isFinite(seconds) && seconds >= 0) {
|
|
275
|
+
return seconds * 1e3;
|
|
276
|
+
}
|
|
277
|
+
const retryAt = Date.parse(value);
|
|
278
|
+
if (!Number.isNaN(retryAt)) {
|
|
279
|
+
return Math.max(0, retryAt - Date.now());
|
|
280
|
+
}
|
|
281
|
+
return void 0;
|
|
282
|
+
}
|
|
247
283
|
|
|
248
284
|
// src/files/resolve-input.ts
|
|
249
285
|
import { readFile } from "fs/promises";
|
|
@@ -275,16 +311,19 @@ async function streamToBuffer(stream) {
|
|
|
275
311
|
for await (const chunk of stream) chunks.push(Buffer.from(chunk));
|
|
276
312
|
return Buffer.concat(chunks);
|
|
277
313
|
}
|
|
314
|
+
function toBlobBytes(bytes) {
|
|
315
|
+
return new Uint8Array(bytes);
|
|
316
|
+
}
|
|
278
317
|
async function resolveOne(input, fallbackName) {
|
|
279
318
|
if (typeof input === "string" || isPathInput(input)) {
|
|
280
319
|
const path = typeof input === "string" ? input : input.path;
|
|
281
320
|
const data = await readFile(path);
|
|
282
321
|
const filename = basename(path);
|
|
283
|
-
return { data: new Blob([data]), filename, contentType: contentTypeFor(filename) };
|
|
322
|
+
return { data: new Blob([toBlobBytes(data)]), filename, contentType: contentTypeFor(filename) };
|
|
284
323
|
}
|
|
285
324
|
if (input instanceof Uint8Array) {
|
|
286
325
|
return {
|
|
287
|
-
data: new Blob([input]),
|
|
326
|
+
data: new Blob([toBlobBytes(input)]),
|
|
288
327
|
filename: fallbackName,
|
|
289
328
|
contentType: contentTypeFor(fallbackName)
|
|
290
329
|
};
|
|
@@ -292,7 +331,7 @@ async function resolveOne(input, fallbackName) {
|
|
|
292
331
|
if (isReadable(input)) {
|
|
293
332
|
const buf = await streamToBuffer(input);
|
|
294
333
|
return {
|
|
295
|
-
data: new Blob([buf]),
|
|
334
|
+
data: new Blob([toBlobBytes(buf)]),
|
|
296
335
|
filename: fallbackName,
|
|
297
336
|
contentType: contentTypeFor(fallbackName)
|
|
298
337
|
};
|
|
@@ -306,38 +345,43 @@ async function resolveInputs(input) {
|
|
|
306
345
|
return Promise.all(list.map((item) => resolveOne(item, fallback)));
|
|
307
346
|
}
|
|
308
347
|
|
|
309
|
-
// src/api/
|
|
310
|
-
async function
|
|
348
|
+
// src/api/run-flow.ts
|
|
349
|
+
async function runFlow(http, flowId, parts, signal, useDraftVersion = false, context, callbackUrl, language) {
|
|
311
350
|
const form = new FormData();
|
|
312
351
|
for (const part of parts) {
|
|
313
352
|
form.append("files", new File([part.data], part.filename, { type: part.contentType }));
|
|
314
353
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
const res = await http.requestJson("POST", path, {
|
|
325
|
-
query: { async_mode: "true" },
|
|
326
|
-
headers: { "content-type": "application/json" },
|
|
327
|
-
body: JSON.stringify(body),
|
|
328
|
-
signal
|
|
329
|
-
});
|
|
330
|
-
return res.execution_result_id;
|
|
354
|
+
if (context !== void 0) form.append("json_context", JSON.stringify(context));
|
|
355
|
+
if (callbackUrl !== void 0) form.append("callback_url", callbackUrl);
|
|
356
|
+
if (language !== void 0) form.append("language", language);
|
|
357
|
+
if (useDraftVersion) form.append("useDraftVersion", "true");
|
|
358
|
+
return http.requestJson(
|
|
359
|
+
"POST",
|
|
360
|
+
`/flows/${flowId}/execute-async`,
|
|
361
|
+
{ body: form, signal }
|
|
362
|
+
);
|
|
331
363
|
}
|
|
332
364
|
|
|
333
365
|
// src/api/execution-status.ts
|
|
334
366
|
async function getStatus(http, executionResultId, signal) {
|
|
335
|
-
|
|
367
|
+
const res = await http.requestRaw("GET", `/executions/${executionResultId}/status`, { signal });
|
|
368
|
+
const text = await res.text();
|
|
369
|
+
const body = text ? JSON.parse(text) : { status: "" };
|
|
370
|
+
const pollAfterMs = parsePositiveInt(res.headers.get("x-poll-after"));
|
|
371
|
+
return pollAfterMs === void 0 ? body : { ...body, pollAfterMs };
|
|
372
|
+
}
|
|
373
|
+
function parsePositiveInt(value) {
|
|
374
|
+
if (!value) return void 0;
|
|
375
|
+
const parsed = Number(value);
|
|
376
|
+
return Number.isFinite(parsed) && parsed >= 0 ? parsed : void 0;
|
|
336
377
|
}
|
|
337
378
|
|
|
338
379
|
// src/api/execution-result.ts
|
|
339
|
-
async function getResult(http, executionResultId, signal) {
|
|
340
|
-
return http.requestJson("GET", `/
|
|
380
|
+
async function getResult(http, executionResultId, signal, language) {
|
|
381
|
+
return http.requestJson("GET", `/executions/${executionResultId}/result`, {
|
|
382
|
+
query: language ? { language } : void 0,
|
|
383
|
+
signal
|
|
384
|
+
});
|
|
341
385
|
}
|
|
342
386
|
|
|
343
387
|
// src/polling/poll.ts
|
|
@@ -347,166 +391,42 @@ async function pollUntilTerminal(opts) {
|
|
|
347
391
|
const sleep = opts.sleep ?? defaultSleep;
|
|
348
392
|
const start = Date.now();
|
|
349
393
|
const elapsed = opts.elapsed ?? (() => Date.now() - start);
|
|
394
|
+
const random = opts.random ?? Math.random;
|
|
395
|
+
let fallbackAttempt = 0;
|
|
350
396
|
for (; ; ) {
|
|
351
397
|
if (opts.signal?.aborted) throw new DocmanaAbortError("Polling aborted");
|
|
352
|
-
|
|
398
|
+
let result;
|
|
399
|
+
try {
|
|
400
|
+
result = normalizePollResult(await opts.check());
|
|
401
|
+
} catch (err) {
|
|
402
|
+
if (!(err instanceof DocmanaRateLimitError)) throw err;
|
|
403
|
+
if (elapsed() >= opts.timeoutMs) {
|
|
404
|
+
throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
|
|
405
|
+
}
|
|
406
|
+
await sleep(err.retryAfterMs ?? opts.intervalMs);
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const { status } = result;
|
|
353
410
|
if (TERMINAL.has(status)) return status;
|
|
354
411
|
if (elapsed() >= opts.timeoutMs) {
|
|
355
412
|
throw new DocmanaTimeoutError(`Execution did not finish within ${opts.timeoutMs}ms`);
|
|
356
413
|
}
|
|
357
|
-
|
|
414
|
+
const fallbackDelay = fullJitterDelay(
|
|
415
|
+
opts.intervalMs,
|
|
416
|
+
opts.maxIntervalMs,
|
|
417
|
+
fallbackAttempt,
|
|
418
|
+
random
|
|
419
|
+
);
|
|
420
|
+
fallbackAttempt += 1;
|
|
421
|
+
await sleep(result.nextDelayMs ?? fallbackDelay);
|
|
358
422
|
}
|
|
359
423
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
function extractNodeFields(rawNode, targetLanguage) {
|
|
363
|
-
const metadataFields = /* @__PURE__ */ new Set([
|
|
364
|
-
"node_id",
|
|
365
|
-
"node_name",
|
|
366
|
-
"node_type",
|
|
367
|
-
"feature_name",
|
|
368
|
-
"running_time",
|
|
369
|
-
"status",
|
|
370
|
-
"score",
|
|
371
|
-
"feedback",
|
|
372
|
-
"translations",
|
|
373
|
-
"tokens",
|
|
374
|
-
"input_tokens",
|
|
375
|
-
"output_tokens",
|
|
376
|
-
"errors",
|
|
377
|
-
"mana",
|
|
378
|
-
"model",
|
|
379
|
-
"tool_calls",
|
|
380
|
-
"prompt_id",
|
|
381
|
-
"prompt",
|
|
382
|
-
"prompt_name",
|
|
383
|
-
"label",
|
|
384
|
-
"scoreDescription",
|
|
385
|
-
"score_description",
|
|
386
|
-
"result",
|
|
387
|
-
"documentType"
|
|
388
|
-
]);
|
|
389
|
-
let sourceObj = rawNode;
|
|
390
|
-
if (targetLanguage) {
|
|
391
|
-
const translations = rawNode.translations;
|
|
392
|
-
if (!translations || !translations[targetLanguage]) {
|
|
393
|
-
throw new DocmanaError(
|
|
394
|
-
`Translation for language '${targetLanguage}' is missing on node '${rawNode.node_name || rawNode.node_id}'`,
|
|
395
|
-
{ code: "translation_missing", status: 400 }
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
sourceObj = translations[targetLanguage];
|
|
399
|
-
}
|
|
400
|
-
const dynamicFields = {};
|
|
401
|
-
for (const [key, value] of Object.entries(sourceObj)) {
|
|
402
|
-
if (!metadataFields.has(key)) {
|
|
403
|
-
dynamicFields[key] = value;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
const sdkNode = {
|
|
407
|
-
status: rawNode.status || "Unknown"
|
|
408
|
-
};
|
|
409
|
-
if (rawNode.errors && rawNode.errors.length > 0) {
|
|
410
|
-
sdkNode.errors = rawNode.errors;
|
|
411
|
-
}
|
|
412
|
-
const nodeType = rawNode.node_type || "";
|
|
413
|
-
const isScoreNode = /^(Validation|Classification|CrossValidation|Conclusion|Finish)$/i.test(
|
|
414
|
-
nodeType
|
|
415
|
-
);
|
|
416
|
-
if (isScoreNode && rawNode.score !== void 0 && rawNode.score !== null) {
|
|
417
|
-
sdkNode.score = rawNode.score;
|
|
418
|
-
}
|
|
419
|
-
const feedback = sourceObj.feedback ?? rawNode.feedback;
|
|
420
|
-
if (feedback !== void 0 && feedback !== null) {
|
|
421
|
-
sdkNode.feedback = feedback;
|
|
422
|
-
}
|
|
423
|
-
if (Object.keys(dynamicFields).length > 0) {
|
|
424
|
-
sdkNode.data = dynamicFields;
|
|
425
|
-
}
|
|
426
|
-
return sdkNode;
|
|
424
|
+
function normalizePollResult(result) {
|
|
425
|
+
return typeof result === "string" ? { status: result } : result;
|
|
427
426
|
}
|
|
428
|
-
function
|
|
429
|
-
const
|
|
430
|
-
|
|
431
|
-
executionId: String(raw.execution_id || raw.executionResultId || raw.execution_result_id || ""),
|
|
432
|
-
documents: {
|
|
433
|
-
mappings: []
|
|
434
|
-
}
|
|
435
|
-
};
|
|
436
|
-
if (raw.errors && raw.errors.length > 0) {
|
|
437
|
-
sdkResult.errors = raw.errors;
|
|
438
|
-
}
|
|
439
|
-
const results = raw.results || [];
|
|
440
|
-
for (const item of results) {
|
|
441
|
-
const isDocSpecific = !!item.document;
|
|
442
|
-
if (isDocSpecific && item.document) {
|
|
443
|
-
const docReq = item.document;
|
|
444
|
-
const docKey = docReq.doc_key || "unknown_doc";
|
|
445
|
-
const docName = docReq.name || "";
|
|
446
|
-
sdkResult.documents.mappings.push({
|
|
447
|
-
name: docName,
|
|
448
|
-
reference: docKey
|
|
449
|
-
});
|
|
450
|
-
const docResult = {
|
|
451
|
-
status: item.status || docReq.status || "Unknown",
|
|
452
|
-
extractions: {},
|
|
453
|
-
validations: {}
|
|
454
|
-
};
|
|
455
|
-
const nodeResults = item.node_results || [];
|
|
456
|
-
for (const node of nodeResults) {
|
|
457
|
-
const nodeType = node.node_type || "";
|
|
458
|
-
const nodeName = node.node_name || node.node_id || "unknown_node";
|
|
459
|
-
if (/^Classification$/i.test(nodeType)) {
|
|
460
|
-
const mappedNode = extractNodeFields(node, targetLanguage);
|
|
461
|
-
if (!docResult.classifications) {
|
|
462
|
-
docResult.classifications = {};
|
|
463
|
-
}
|
|
464
|
-
docResult.classifications[nodeName] = mappedNode;
|
|
465
|
-
} else if (/^MetadataExtraction$/i.test(nodeType)) {
|
|
466
|
-
docResult.metadataExtraction = extractNodeFields(node, targetLanguage);
|
|
467
|
-
} else if (/^Extraction$/i.test(nodeType)) {
|
|
468
|
-
if (docResult.extractions) {
|
|
469
|
-
docResult.extractions[nodeName] = extractNodeFields(node, targetLanguage);
|
|
470
|
-
}
|
|
471
|
-
} else if (/^Validation$/i.test(nodeType)) {
|
|
472
|
-
if (docResult.validations) {
|
|
473
|
-
docResult.validations[nodeName] = extractNodeFields(node, targetLanguage);
|
|
474
|
-
}
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
if (docResult.extractions && Object.keys(docResult.extractions).length === 0) {
|
|
478
|
-
delete docResult.extractions;
|
|
479
|
-
}
|
|
480
|
-
if (docResult.validations && Object.keys(docResult.validations).length === 0) {
|
|
481
|
-
delete docResult.validations;
|
|
482
|
-
}
|
|
483
|
-
sdkResult.documents[docKey] = docResult;
|
|
484
|
-
} else {
|
|
485
|
-
const nodeResults = item.node_results || [];
|
|
486
|
-
for (const node of nodeResults) {
|
|
487
|
-
const nodeType = node.node_type || "";
|
|
488
|
-
const nodeName = node.node_name || node.node_id || "unknown_node";
|
|
489
|
-
if (/^CrossValidation$/i.test(nodeType)) {
|
|
490
|
-
if (!sdkResult.crossValidations) {
|
|
491
|
-
sdkResult.crossValidations = {};
|
|
492
|
-
}
|
|
493
|
-
sdkResult.crossValidations[nodeName] = extractNodeFields(
|
|
494
|
-
node,
|
|
495
|
-
targetLanguage
|
|
496
|
-
);
|
|
497
|
-
} else if (/^(Conclusion|Finish)$/i.test(nodeType)) {
|
|
498
|
-
if (!sdkResult.conclusions) {
|
|
499
|
-
sdkResult.conclusions = {};
|
|
500
|
-
}
|
|
501
|
-
sdkResult.conclusions[nodeName] = extractNodeFields(
|
|
502
|
-
node,
|
|
503
|
-
targetLanguage
|
|
504
|
-
);
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
return sdkResult;
|
|
427
|
+
function fullJitterDelay(intervalMs, maxIntervalMs, attempt, random) {
|
|
428
|
+
const capDelay = Math.min(maxIntervalMs, intervalMs * 2 ** attempt);
|
|
429
|
+
return Math.round(intervalMs + random() * (capDelay - intervalMs));
|
|
510
430
|
}
|
|
511
431
|
|
|
512
432
|
// src/client.ts
|
|
@@ -515,46 +435,79 @@ var Docmana = class {
|
|
|
515
435
|
http;
|
|
516
436
|
constructor(config) {
|
|
517
437
|
this.config = resolveConfig(config);
|
|
518
|
-
const tokenManager = new TokenManager({
|
|
519
|
-
clientId: this.config.clientId,
|
|
520
|
-
clientSecret: this.config.clientSecret,
|
|
521
|
-
tokenEndpoint: this.config.tokenEndpoint,
|
|
522
|
-
scope: this.config.scope,
|
|
523
|
-
fetchImpl: this.config.fetchImpl,
|
|
524
|
-
tokenCache: this.config.tokenCache
|
|
525
|
-
});
|
|
526
438
|
this.http = new HttpClient({
|
|
527
439
|
apiBaseUrl: this.config.apiBaseUrl,
|
|
528
440
|
fetchImpl: this.config.fetchImpl,
|
|
529
|
-
|
|
441
|
+
accessToken: this.config.accessToken,
|
|
442
|
+
getAccessToken: this.config.getAccessToken,
|
|
530
443
|
headers: this.config.headers
|
|
531
444
|
});
|
|
532
445
|
}
|
|
533
446
|
async runFlowAsync(flowId, input) {
|
|
534
447
|
const parts = await resolveInputs(input);
|
|
535
|
-
|
|
536
|
-
const executionResultId = await runFlow(
|
|
448
|
+
return runFlow(
|
|
537
449
|
this.http,
|
|
538
450
|
flowId,
|
|
539
|
-
|
|
451
|
+
parts,
|
|
540
452
|
input.signal,
|
|
541
|
-
input.
|
|
542
|
-
input.context
|
|
453
|
+
input.useDraftVersion ?? false,
|
|
454
|
+
input.context,
|
|
455
|
+
void 0,
|
|
456
|
+
input.language
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
async runFlowWithCallback(flowId, input) {
|
|
460
|
+
const parts = await resolveInputs(input);
|
|
461
|
+
return runFlow(
|
|
462
|
+
this.http,
|
|
463
|
+
flowId,
|
|
464
|
+
parts,
|
|
465
|
+
input.signal,
|
|
466
|
+
input.useDraftVersion ?? false,
|
|
467
|
+
input.context,
|
|
468
|
+
input.callbackUrl,
|
|
469
|
+
input.language
|
|
543
470
|
);
|
|
544
|
-
return { executionResultId, fileIds };
|
|
545
471
|
}
|
|
546
472
|
async getStatus(executionResultId, signal) {
|
|
547
473
|
return getStatus(this.http, executionResultId, signal);
|
|
548
474
|
}
|
|
549
475
|
async getResult(executionResultId, signal, language) {
|
|
550
|
-
|
|
551
|
-
return mapExecutionResult(rawResult, language);
|
|
476
|
+
return getResult(this.http, executionResultId, signal, language);
|
|
552
477
|
}
|
|
553
478
|
async runFlow(flowId, input) {
|
|
554
479
|
const { executionResultId } = await this.runFlowAsync(flowId, input);
|
|
480
|
+
let attempt = 0;
|
|
481
|
+
const startedAt = Date.now();
|
|
555
482
|
await pollUntilTerminal({
|
|
556
|
-
check: async () =>
|
|
483
|
+
check: async () => {
|
|
484
|
+
attempt += 1;
|
|
485
|
+
try {
|
|
486
|
+
const statusResult = await this.getStatus(executionResultId, input.signal);
|
|
487
|
+
const status = statusResult.status;
|
|
488
|
+
const nextPollAfterMs = this.isTerminalStatus(status) ? void 0 : statusResult.pollAfterMs;
|
|
489
|
+
input.onPoll?.({
|
|
490
|
+
executionResultId,
|
|
491
|
+
status,
|
|
492
|
+
attempt,
|
|
493
|
+
elapsedMs: Date.now() - startedAt,
|
|
494
|
+
nextPollAfterMs
|
|
495
|
+
});
|
|
496
|
+
return { status, nextDelayMs: nextPollAfterMs };
|
|
497
|
+
} catch (err) {
|
|
498
|
+
if (err instanceof DocmanaRateLimitError) {
|
|
499
|
+
input.onRateLimit?.({
|
|
500
|
+
executionResultId,
|
|
501
|
+
attempt,
|
|
502
|
+
elapsedMs: Date.now() - startedAt,
|
|
503
|
+
retryAfterMs: err.retryAfterMs ?? input.pollIntervalMs ?? this.config.pollIntervalMs
|
|
504
|
+
});
|
|
505
|
+
}
|
|
506
|
+
throw err;
|
|
507
|
+
}
|
|
508
|
+
},
|
|
557
509
|
intervalMs: input.pollIntervalMs ?? this.config.pollIntervalMs,
|
|
510
|
+
maxIntervalMs: input.pollMaxIntervalMs ?? this.config.pollMaxIntervalMs,
|
|
558
511
|
timeoutMs: input.timeoutMs ?? this.config.timeoutMs,
|
|
559
512
|
signal: input.signal
|
|
560
513
|
});
|
|
@@ -568,6 +521,9 @@ var Docmana = class {
|
|
|
568
521
|
}
|
|
569
522
|
return result;
|
|
570
523
|
}
|
|
524
|
+
isTerminalStatus(status) {
|
|
525
|
+
return status === "Completed" || status === "Failed";
|
|
526
|
+
}
|
|
571
527
|
};
|
|
572
528
|
|
|
573
529
|
// src/cli.ts
|
|
@@ -638,7 +594,7 @@ function buildProgram(env, io, clientFactory, deps) {
|
|
|
638
594
|
const flow = program.command("flow").description(HELP.flow).action(() => {
|
|
639
595
|
io.stdout(flow.helpInformation());
|
|
640
596
|
});
|
|
641
|
-
flow.command("run").description(HELP.run).argument("<flow-id>", "Docmana flow id").requiredOption("--files <csv>", "Comma-separated file paths to upload").option("--
|
|
597
|
+
flow.command("run").description(HELP.run).argument("<flow-id>", "Docmana flow id").requiredOption("--files <csv>", "Comma-separated file paths to upload").option("--api-url <url>", "Consumer API base URL; defaults to DOCMANA_API_URL").option("--access-token <token>", "Bearer token; defaults to DOCMANA_ACCESS_TOKEN").option("--config <path>", `Config file path; defaults to ./${DEFAULT_CONFIG_FILE}`).option("--token-cache <path>", `Token cache path; defaults to ./${DEFAULT_TOKEN_CACHE_FILE}`).option("--json", "Print the raw JSON result").option("--language <lang>", "Translate output to specified language").option("--draft", "Run the current draft flow version").action(async (flowId, options) => {
|
|
642
598
|
await runFlowCommand(flowId, options, env, io, clientFactory, deps);
|
|
643
599
|
});
|
|
644
600
|
const config = program.command("config").description(HELP.config).action(() => {
|
|
@@ -653,46 +609,35 @@ async function runFlowCommand(flowId, options, env, io, clientFactory, deps) {
|
|
|
653
609
|
try {
|
|
654
610
|
const files = parseFiles(options.files);
|
|
655
611
|
const cliConfig = await loadCliConfig(options.config, getCwd(deps), Boolean(options.config));
|
|
656
|
-
const clientId = firstNonEmpty(options.clientId, env.DOCMANA_CLIENT_ID, cliConfig.clientId);
|
|
657
|
-
const clientSecret = firstNonEmpty(
|
|
658
|
-
options.clientSecret,
|
|
659
|
-
env.DOCMANA_CLIENT_SECRET,
|
|
660
|
-
cliConfig.clientSecret
|
|
661
|
-
);
|
|
662
|
-
const organisation = firstNonEmpty(
|
|
663
|
-
options.organisation,
|
|
664
|
-
env.DOCMANA_ORGANISATION,
|
|
665
|
-
cliConfig.organisation
|
|
666
|
-
);
|
|
667
612
|
const apiBaseUrl = firstNonEmpty(options.apiUrl, env.DOCMANA_API_URL, cliConfig.apiBaseUrl);
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
env.DOCMANA_TOKEN_URL,
|
|
671
|
-
cliConfig.tokenEndpoint
|
|
672
|
-
);
|
|
673
|
-
const scope = firstNonEmpty(options.scope, env.DOCMANA_SCOPE, cliConfig.scope);
|
|
674
|
-
if (!clientId)
|
|
675
|
-
throw new CliUsageError("Missing client id. Use --client-id or DOCMANA_CLIENT_ID.");
|
|
676
|
-
if (!clientSecret) {
|
|
677
|
-
throw new CliUsageError(
|
|
678
|
-
"Missing client secret. Use --client-secret or DOCMANA_CLIENT_SECRET."
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
if (!organisation) {
|
|
682
|
-
throw new CliUsageError("Missing organisation. Use --organisation or DOCMANA_ORGANISATION.");
|
|
613
|
+
if (!apiBaseUrl) {
|
|
614
|
+
throw new CliUsageError("Missing API base URL. Use --api-url or DOCMANA_API_URL.");
|
|
683
615
|
}
|
|
616
|
+
const tokenCache = createFileTokenCache(
|
|
617
|
+
resolveTokenCachePath(options.tokenCache, getCwd(deps))
|
|
618
|
+
);
|
|
619
|
+
const accessToken = firstNonEmpty(options.accessToken, env.DOCMANA_ACCESS_TOKEN) ?? await resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io);
|
|
684
620
|
const client = clientFactory({
|
|
685
|
-
clientId,
|
|
686
|
-
clientSecret,
|
|
687
621
|
apiBaseUrl,
|
|
688
|
-
|
|
689
|
-
scope,
|
|
690
|
-
headers: { "X-Selected-Organization": organisation },
|
|
691
|
-
tokenCache: createFileTokenCache(resolveTokenCachePath(options.tokenCache, getCwd(deps)))
|
|
622
|
+
accessToken
|
|
692
623
|
});
|
|
693
624
|
const result = await client.runFlow(flowId, {
|
|
694
625
|
files: files.map((path) => ({ path })),
|
|
695
|
-
language: options.language
|
|
626
|
+
language: options.language,
|
|
627
|
+
useDraftVersion: options.draft === true,
|
|
628
|
+
onPoll: (event) => {
|
|
629
|
+
const nextPoll = event.nextPollAfterMs === void 0 ? "" : `, next poll in ${formatElapsedSeconds(event.nextPollAfterMs)}`;
|
|
630
|
+
io.stderr(
|
|
631
|
+
`Polling execution ${event.executionResultId}: ${event.status} (attempt ${event.attempt}, elapsed ${formatElapsedSeconds(event.elapsedMs)}${nextPoll})
|
|
632
|
+
`
|
|
633
|
+
);
|
|
634
|
+
},
|
|
635
|
+
onRateLimit: (event) => {
|
|
636
|
+
io.stderr(
|
|
637
|
+
`Polling execution ${event.executionResultId}: rate limited (attempt ${event.attempt}, retrying in ${formatElapsedSeconds(event.retryAfterMs)})
|
|
638
|
+
`
|
|
639
|
+
);
|
|
640
|
+
}
|
|
696
641
|
});
|
|
697
642
|
if (options.json) {
|
|
698
643
|
io.stdout(`${JSON.stringify(result, null, 2)}
|
|
@@ -722,10 +667,38 @@ async function runFlowCommand(flowId, options, env, io, clientFactory, deps) {
|
|
|
722
667
|
throw err;
|
|
723
668
|
}
|
|
724
669
|
}
|
|
670
|
+
async function resolveFlowAccessToken(env, cliConfig, tokenCache, deps, io) {
|
|
671
|
+
const cached = await readFreshCachedToken(tokenCache);
|
|
672
|
+
if (cached) return cached;
|
|
673
|
+
const clientId = firstNonEmpty(env.DOCMANA_CLIENT_ID, cliConfig.clientId);
|
|
674
|
+
const clientSecret = firstNonEmpty(env.DOCMANA_CLIENT_SECRET, cliConfig.clientSecret);
|
|
675
|
+
if (!clientId || !clientSecret) {
|
|
676
|
+
throw new CliUsageError(
|
|
677
|
+
"Missing access token. Use --access-token, DOCMANA_ACCESS_TOKEN, docmana login, or configure OAuth credentials."
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
const tokenManager = new TokenManager({
|
|
681
|
+
clientId,
|
|
682
|
+
clientSecret,
|
|
683
|
+
tokenEndpoint: firstNonEmpty(env.DOCMANA_TOKEN_URL, cliConfig.tokenEndpoint) ?? DEFAULTS.tokenEndpoint,
|
|
684
|
+
scope: firstNonEmpty(env.DOCMANA_SCOPE, cliConfig.scope) ?? DEFAULTS.scope,
|
|
685
|
+
fetchImpl: deps.fetch ?? fetch,
|
|
686
|
+
tokenCache
|
|
687
|
+
});
|
|
688
|
+
const token = await tokenManager.getToken();
|
|
689
|
+
io.stderr("Generated new access token and updated the token cache.\n");
|
|
690
|
+
return token;
|
|
691
|
+
}
|
|
692
|
+
async function readFreshCachedToken(tokenCache) {
|
|
693
|
+
const cached = await tokenCache.read().catch(() => null);
|
|
694
|
+
if (!cached || Date.now() >= cached.expiresAt) return void 0;
|
|
695
|
+
return cached.accessToken;
|
|
696
|
+
}
|
|
725
697
|
async function loginCommand(options, env, io, deps) {
|
|
726
698
|
const auth = await resolveAuthConfig(options, env, deps);
|
|
727
699
|
const tokenCachePath = resolveTokenCachePath(options.tokenCache, getCwd(deps));
|
|
728
700
|
const tokenCache = createFileTokenCache(tokenCachePath);
|
|
701
|
+
const hadFreshCachedToken = Boolean(await readFreshCachedToken(tokenCache));
|
|
729
702
|
const tokenManager = new TokenManager({
|
|
730
703
|
clientId: auth.clientId,
|
|
731
704
|
clientSecret: auth.clientSecret,
|
|
@@ -736,6 +709,9 @@ async function loginCommand(options, env, io, deps) {
|
|
|
736
709
|
});
|
|
737
710
|
await tokenManager.getToken();
|
|
738
711
|
const cached = await tokenCache.read();
|
|
712
|
+
if (!hadFreshCachedToken) {
|
|
713
|
+
io.stdout("Generated new access token and updated the token cache.\n");
|
|
714
|
+
}
|
|
739
715
|
io.stdout(`Logged in. Token cached at ${tokenCachePath}
|
|
740
716
|
`);
|
|
741
717
|
if (cached) io.stdout(formatTokenExpiry(cached.expiresAt));
|
|
@@ -774,7 +750,7 @@ async function configInitCommand(options, io, deps) {
|
|
|
774
750
|
try {
|
|
775
751
|
const config = {
|
|
776
752
|
apiBaseUrl: await prompt("API base URL", {
|
|
777
|
-
defaultValue: existing.apiBaseUrl
|
|
753
|
+
defaultValue: existing.apiBaseUrl
|
|
778
754
|
}),
|
|
779
755
|
tokenEndpoint: await prompt("OAuth2 token endpoint", {
|
|
780
756
|
defaultValue: existing.tokenEndpoint ?? DEFAULTS.tokenEndpoint
|
|
@@ -782,9 +758,6 @@ async function configInitCommand(options, io, deps) {
|
|
|
782
758
|
scope: await prompt("OAuth2 scope", {
|
|
783
759
|
defaultValue: existing.scope ?? DEFAULTS.scope
|
|
784
760
|
}),
|
|
785
|
-
organisation: await prompt("Organisation", {
|
|
786
|
-
defaultValue: existing.organisation
|
|
787
|
-
}),
|
|
788
761
|
clientId: await prompt("Client id", {
|
|
789
762
|
defaultValue: existing.clientId
|
|
790
763
|
}),
|
|
@@ -833,7 +806,6 @@ function normalizeCliConfig(config) {
|
|
|
833
806
|
apiBaseUrl: stringValue(config.apiBaseUrl),
|
|
834
807
|
tokenEndpoint: stringValue(config.tokenEndpoint),
|
|
835
808
|
scope: stringValue(config.scope),
|
|
836
|
-
organisation: stringValue(config.organisation),
|
|
837
809
|
clientId: stringValue(config.clientId),
|
|
838
810
|
clientSecret: stringValue(config.clientSecret)
|
|
839
811
|
};
|
|
@@ -909,6 +881,9 @@ function formatPrompt(label, options) {
|
|
|
909
881
|
const defaultHint = options.defaultValue && !options.secret ? ` [${options.defaultValue}]` : options.defaultValue && options.secret ? " [current value hidden]" : "";
|
|
910
882
|
return `${label}${defaultHint}: `;
|
|
911
883
|
}
|
|
884
|
+
function formatElapsedSeconds(elapsedMs) {
|
|
885
|
+
return `${Math.floor(elapsedMs / 1e3)}s`;
|
|
886
|
+
}
|
|
912
887
|
async function readStdinLines() {
|
|
913
888
|
let input = "";
|
|
914
889
|
for await (const chunk of process.stdin) input += String(chunk);
|