@trydreamstate/cli 1.3.13 → 1.3.15
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 +5 -1
- package/agent-plugin/.claude-plugin/plugin.json +1 -1
- package/agent-plugin/.codex-plugin/plugin.json +1 -1
- package/agent-plugin/skills/diagnostics/SKILL.md +2 -0
- package/agent-plugin/skills/first-win/SKILL.md +1 -1
- package/agent-plugin/skills/list-building/SKILL.md +4 -0
- package/agent-plugin/skills/list-building/column-authoring.md +1 -1
- package/agent-plugin/skills/list-building/incoming-people.md +2 -2
- package/agent-plugin/skills/outreach/SKILL.md +6 -6
- package/agent-plugin/skills/outreach/enrollment.md +3 -3
- package/agent-plugin/skills/outreach/message-copy.md +1 -1
- package/agent-plugin/skills/social/SKILL.md +6 -2
- package/agent-plugin/skills/workflows/lifecycle-and-enrollment.md +1 -1
- package/dist/client.d.ts +2 -2
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +48 -5
- package/dist/commands/engage.d.ts +2 -0
- package/dist/commands/engage.d.ts.map +1 -0
- package/dist/commands/engage.js +6 -0
- package/dist/commands/genericPack.d.ts +1 -1
- package/dist/commands/genericPack.d.ts.map +1 -1
- package/dist/commands/shared.d.ts +1 -1
- package/dist/commands/shared.d.ts.map +1 -1
- package/dist/commands/shared.js +14 -4
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +9 -0
- package/dist/errors.d.ts +8 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +4 -0
- package/dist/gatedRun.d.ts +13 -5
- package/dist/gatedRun.d.ts.map +1 -1
- package/dist/gatedRun.js +46 -11
- package/dist/generated/packVerbs.generated.json +1 -5
- package/dist/idempotency.d.ts.map +1 -1
- package/dist/idempotency.js +47 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -5
- package/dist/runEnvelopeStatus.d.ts +53 -0
- package/dist/runEnvelopeStatus.d.ts.map +1 -0
- package/dist/runEnvelopeStatus.js +83 -0
- package/dist/runWait.d.ts.map +1 -1
- package/dist/runWait.js +9 -11
- package/dist/skillInstaller.d.ts.map +1 -1
- package/dist/skillInstaller.js +10 -0
- package/dist/tableFilePreview.d.ts.map +1 -1
- package/dist/tableFilePreview.js +21 -3
- package/dist/tablesImport.d.ts.map +1 -1
- package/dist/tablesImport.js +13 -3
- package/dist/turnCache.d.ts +12 -1
- package/dist/turnCache.d.ts.map +1 -1
- package/dist/turnCache.js +104 -25
- package/package.json +1 -1
package/dist/tableFilePreview.js
CHANGED
|
@@ -203,7 +203,7 @@ function unzipEntries(buffer) {
|
|
|
203
203
|
const localOffset = buffer.readUInt32LE(offset + 42);
|
|
204
204
|
const name = buffer.toString('utf8', offset + 46, offset + 46 + nameLength);
|
|
205
205
|
offset += 46 + nameLength + extraLength + commentLength;
|
|
206
|
-
if (!/xl\/(sharedStrings\.xml|worksheets\/sheet\d+\.xml)$/.test(name))
|
|
206
|
+
if (!/xl\/(sharedStrings\.xml|workbook\.xml|_rels\/workbook\.xml\.rels|worksheets\/sheet\d+\.xml)$/.test(name))
|
|
207
207
|
continue;
|
|
208
208
|
if (localOffset + 30 > buffer.length)
|
|
209
209
|
throw new Error('xlsx local header is outside the file');
|
|
@@ -243,9 +243,23 @@ function sharedStringTable(xml) {
|
|
|
243
243
|
*/
|
|
244
244
|
function readXlsxRows(buffer, rowLimit) {
|
|
245
245
|
const entries = unzipEntries(buffer);
|
|
246
|
-
|
|
246
|
+
let sheetName = [...entries.keys()]
|
|
247
247
|
.filter((name) => /xl\/worksheets\/sheet\d+\.xml$/.test(name))
|
|
248
248
|
.sort()[0];
|
|
249
|
+
const workbook = entries.get('xl/workbook.xml');
|
|
250
|
+
const relationships = entries.get('xl/_rels/workbook.xml.rels');
|
|
251
|
+
if (workbook && relationships) {
|
|
252
|
+
const sheet = /<(?:[\w-]+:)?sheet\b([^>]*)\/?\s*>/.exec(workbook.toString('utf8'));
|
|
253
|
+
const id = /\br:id=["']([^"']+)["']/.exec(sheet?.[1] ?? '')?.[1];
|
|
254
|
+
const relationship = [...relationships.toString('utf8').matchAll(/<(?:[\w-]+:)?Relationship\b([^>]*)\/?\s*>/g)]
|
|
255
|
+
.find((match) => /\bId=["']([^"']+)["']/.exec(match[1])?.[1] === id);
|
|
256
|
+
const target = /\bTarget=["']([^"']+)["']/.exec(relationship?.[1] ?? '')?.[1];
|
|
257
|
+
if (!id || !target)
|
|
258
|
+
throw new Error('xlsx file has an invalid first worksheet relationship');
|
|
259
|
+
sheetName = target.startsWith('/') ? target.slice(1) : `xl/${target.replace(/^\.\//, '')}`;
|
|
260
|
+
if (!entries.has(sheetName))
|
|
261
|
+
throw new Error('xlsx file has a missing first worksheet');
|
|
262
|
+
}
|
|
249
263
|
if (!sheetName)
|
|
250
264
|
throw new Error('xlsx file has no worksheet');
|
|
251
265
|
const sheet = entries.get(sheetName).toString('utf8');
|
|
@@ -282,7 +296,11 @@ function readXlsxRows(buffer, rowLimit) {
|
|
|
282
296
|
const rowNumbers = [...grid.keys()].sort((left, right) => left - right);
|
|
283
297
|
if (rowNumbers.length === 0)
|
|
284
298
|
throw new Error('File has no header row');
|
|
285
|
-
|
|
299
|
+
let width = 0;
|
|
300
|
+
for (const cells of grid.values())
|
|
301
|
+
for (const k of cells.keys())
|
|
302
|
+
if (k + 1 > width)
|
|
303
|
+
width = k + 1;
|
|
286
304
|
const toRow = (cells) => Array.from({ length: width }, (_, index) => cells?.get(index) ?? '');
|
|
287
305
|
const totalRows = [...sheet.matchAll(/<(?:[\w-]+:)?row\b/g)].length;
|
|
288
306
|
return { rows: rowNumbers.map((number) => toRow(grid.get(number))), totalRows };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tablesImport.d.ts","sourceRoot":"","sources":["../src/tablesImport.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAC3C,wEAAwE;AACxE,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAY3C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoB1E;AAMD,6HAA6H;AAC7H,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,CAMf;AAkCD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,
|
|
1
|
+
{"version":3,"file":"tablesImport.d.ts","sourceRoot":"","sources":["../src/tablesImport.ts"],"names":[],"mappings":"AAIA,OAAO,EAAkB,KAAK,eAAe,EAAE,MAAM,YAAY,CAAC;AAIlE;;;;;;;;;;;;;;;GAeG;AAEH,eAAO,MAAM,gBAAgB,OAAQ,CAAC;AACtC,kFAAkF;AAClF,eAAO,MAAM,iBAAiB,UAAY,CAAC;AAC3C,wEAAwE;AACxE,eAAO,MAAM,eAAe,MAAM,CAAC;AACnC,+EAA+E;AAC/E,eAAO,MAAM,mBAAmB,SAAU,CAAC;AAY3C,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,KAAK,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,mBAAmB,CAAC,EAAE,OAAO,CAAC;CAC/B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC/B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;CACpB;AAuBD;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAoB1E;AAMD,6HAA6H;AAC7H,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EACzC,OAAO,EAAE,QAAQ,GAAG,SAAS,GAC5B,MAAM,GAAG,IAAI,CAMf;AAkCD,wBAAsB,gBAAgB,CAAC,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CA0J1H"}
|
package/dist/tablesImport.js
CHANGED
|
@@ -209,18 +209,28 @@ async function importLocalTable(context, options) {
|
|
|
209
209
|
if (!uploadId)
|
|
210
210
|
throw new client_1.CliError('csv_upload_create returned no upload_id.', 'invalid_response', 0, { details: { import_step: 'create' } });
|
|
211
211
|
const mapping = { ...(record(created.suggested_mapping) ?? {}) };
|
|
212
|
+
const definitions = Array.isArray(created.column_definitions) ? created.column_definitions.map(record) : [];
|
|
213
|
+
if (definitions.length !== file.headers.length || definitions.some((definition) => typeof definition?.source_header !== 'string')) {
|
|
214
|
+
throw new client_1.CliError('csv_upload_create returned invalid column definitions.', 'invalid_response', 0);
|
|
215
|
+
}
|
|
216
|
+
const sourceHeaders = definitions.map((definition) => definition.source_header);
|
|
217
|
+
let upsertHeader;
|
|
212
218
|
if (options.upsertKey) {
|
|
213
219
|
if (!file.headers.includes(options.upsertKey)) {
|
|
214
220
|
throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" is not a column of ${fileName}. Columns: ${file.headers.join(', ')}.`);
|
|
215
221
|
}
|
|
216
|
-
|
|
222
|
+
const matches = file.headers.flatMap((header, index) => header === options.upsertKey ? [index] : []);
|
|
223
|
+
if (matches.length > 1)
|
|
224
|
+
throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" matches multiple columns: ${matches.map((index) => index + 1).join(', ')}. Rename the columns.`);
|
|
225
|
+
upsertHeader = sourceHeaders[matches[0]];
|
|
226
|
+
if (!mapping[upsertHeader]) {
|
|
217
227
|
if (Object.values(mapping).includes('external_id')) {
|
|
218
228
|
throw new errors_1.CliUsageError(`--upsert-key "${options.upsertKey}" is not mapped to a field and external_id is already used by another column. Rename the key column to a mappable name (email, linkedin_url, external_id).`);
|
|
219
229
|
}
|
|
220
|
-
mapping[
|
|
230
|
+
mapping[upsertHeader] = 'external_id';
|
|
221
231
|
}
|
|
222
232
|
}
|
|
223
|
-
const identityColumn =
|
|
233
|
+
const identityColumn = upsertHeader ?? chooseIdentityColumn(sourceHeaders, mapping, rowKind);
|
|
224
234
|
if (!identityColumn || Object.keys(mapping).length === 0) {
|
|
225
235
|
throw new errors_1.CliUsageError(`No column of ${fileName} could be matched to an identity field (email, linkedin_url, external_id${rowKind === 'company' ? ', domain' : ''}). The upload ${uploadId} was opened and stays unattached. Pass --upsert-key <column> to choose the identity column.`);
|
|
226
236
|
}
|
package/dist/turnCache.d.ts
CHANGED
|
@@ -21,7 +21,18 @@ export declare function reusableTurn(path: string, baseUrl: string, apiKey: stri
|
|
|
21
21
|
rows?: number | null;
|
|
22
22
|
credits?: string | null;
|
|
23
23
|
} | null>;
|
|
24
|
+
/**
|
|
25
|
+
* The turn token stored for this exact quote_hash. Null when that quote is
|
|
26
|
+
* absent or its fingerprint is not this input: never hand back another quote's token.
|
|
27
|
+
*/
|
|
28
|
+
export declare function reusableTurnForQuote(path: string, baseUrl: string, apiKey: string, capabilityId: string, quoteHash: string, fingerprint: string, now?: number, onWarning?: CacheWarning): Promise<{
|
|
29
|
+
token: string;
|
|
30
|
+
quote_hash: string;
|
|
31
|
+
maximum_credits: number | null;
|
|
32
|
+
rows?: number | null;
|
|
33
|
+
credits?: string | null;
|
|
34
|
+
} | null>;
|
|
24
35
|
export declare function rememberTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, token: string, now?: number, onWarning?: CacheWarning, quote?: CachedQuote): Promise<void>;
|
|
25
|
-
export declare function forgetTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, onWarning?: CacheWarning): Promise<void>;
|
|
36
|
+
export declare function forgetTurnToken(path: string, baseUrl: string, apiKey: string, capabilityId: string, onWarning?: CacheWarning, quoteHash?: string): Promise<void>;
|
|
26
37
|
export {};
|
|
27
38
|
//# sourceMappingURL=turnCache.d.ts.map
|
package/dist/turnCache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"turnCache.d.ts","sourceRoot":"","sources":["../src/turnCache.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"turnCache.d.ts","sourceRoot":"","sources":["../src/turnCache.ts"],"names":[],"mappings":"AAiBA,eAAO,MAAM,aAAa,QAAa,CAAC;AAExC,wPAAwP;AACxP,MAAM,MAAM,WAAW,GAAG;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAItJ,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,cAAc,KAAK,IAAI,CAAC;AAE3D,wBAAgB,aAAa,CAAC,eAAe,EAAE,MAAM,GAAG,MAAM,CAE7D;AAgED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GAC9G,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB;AAED;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GACnI,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAU7I;AAED;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,GACtJ,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,GAAG,IAAI,CAAC,CAMtI;AAED,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,SAAa,EAAE,SAAS,CAAC,EAAE,YAAY,EAC9H,KAAK,CAAC,EAAE,WAAW,GAClB,OAAO,CAAC,IAAI,CAAC,CAkBf;AAED,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,EAAE,SAAS,CAAC,EAAE,MAAM,GAChH,OAAO,CAAC,IAAI,CAAC,CAsBf"}
|
package/dist/turnCache.js
CHANGED
|
@@ -4,6 +4,7 @@ exports.TURN_REUSE_MS = void 0;
|
|
|
4
4
|
exports.turnCachePath = turnCachePath;
|
|
5
5
|
exports.reusableTurnToken = reusableTurnToken;
|
|
6
6
|
exports.reusableTurn = reusableTurn;
|
|
7
|
+
exports.reusableTurnForQuote = reusableTurnForQuote;
|
|
7
8
|
exports.rememberTurnToken = rememberTurnToken;
|
|
8
9
|
exports.forgetTurnToken = forgetTurnToken;
|
|
9
10
|
const node_crypto_1 = require("node:crypto");
|
|
@@ -17,7 +18,9 @@ const node_path_1 = require("node:path");
|
|
|
17
18
|
// refused four times in a row). The cache therefore keeps the rehearsal's turn
|
|
18
19
|
// token together with its quote_hash and a fingerprint of the exact input that
|
|
19
20
|
// was priced, so a later `--live` of the same input can echo it and a changed
|
|
20
|
-
// input never inherits a price it was not shown.
|
|
21
|
+
// input never inherits a price it was not shown. One capability keeps a quote
|
|
22
|
+
// per quote_hash: parallel dry runs of different params must not overwrite
|
|
23
|
+
// each other's token.
|
|
21
24
|
// The server turn lives 10 minutes; reuse for 9 so a cached token never
|
|
22
25
|
// expires mid-request.
|
|
23
26
|
exports.TURN_REUSE_MS = 9 * 60_000;
|
|
@@ -49,6 +52,39 @@ async function persist(path, entries, onWarning) {
|
|
|
49
52
|
onWarning?.(error);
|
|
50
53
|
}
|
|
51
54
|
}
|
|
55
|
+
/** Re-load immediately before persist. `mutate` may change only its own key. */
|
|
56
|
+
async function modifyCache(path, onWarning, mutate) {
|
|
57
|
+
const fresh = await load(path, onWarning);
|
|
58
|
+
if (!mutate(fresh))
|
|
59
|
+
return;
|
|
60
|
+
await persist(path, fresh, onWarning);
|
|
61
|
+
}
|
|
62
|
+
function slots(entry, now) {
|
|
63
|
+
const out = [];
|
|
64
|
+
const seen = new Set();
|
|
65
|
+
for (const slot of Object.values(entry.quotes ?? {})) {
|
|
66
|
+
if (!slot || typeof slot.token !== 'string' || typeof slot.quote?.quote_hash !== 'string')
|
|
67
|
+
continue;
|
|
68
|
+
if (now - slot.at >= exports.TURN_REUSE_MS)
|
|
69
|
+
continue;
|
|
70
|
+
seen.add(slot.quote.quote_hash);
|
|
71
|
+
out.push(slot);
|
|
72
|
+
}
|
|
73
|
+
if (entry.quote?.quote_hash && typeof entry.token === 'string' && now - entry.at < exports.TURN_REUSE_MS && !seen.has(entry.quote.quote_hash)) {
|
|
74
|
+
out.push({ token: entry.token, at: entry.at, quote: entry.quote });
|
|
75
|
+
}
|
|
76
|
+
return out;
|
|
77
|
+
}
|
|
78
|
+
function present(slot) {
|
|
79
|
+
const quote = slot.quote;
|
|
80
|
+
return {
|
|
81
|
+
token: slot.token,
|
|
82
|
+
quote_hash: quote.quote_hash,
|
|
83
|
+
maximum_credits: typeof quote.maximum_credits === 'number' ? quote.maximum_credits : null,
|
|
84
|
+
...(quote.rows !== undefined ? { rows: quote.rows } : {}),
|
|
85
|
+
...(quote.credits !== undefined ? { credits: quote.credits } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
52
88
|
async function reusableTurnToken(path, baseUrl, apiKey, capabilityId, now = Date.now(), onWarning) {
|
|
53
89
|
const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
|
|
54
90
|
return entry && typeof entry.token === 'string' && now - entry.at < exports.TURN_REUSE_MS ? entry.token : null;
|
|
@@ -59,33 +95,76 @@ async function reusableTurnToken(path, baseUrl, apiKey, capabilityId, now = Date
|
|
|
59
95
|
*/
|
|
60
96
|
async function reusableTurn(path, baseUrl, apiKey, capabilityId, fingerprint, now = Date.now(), onWarning) {
|
|
61
97
|
const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
|
|
62
|
-
if (!entry || typeof entry.token !== 'string'
|
|
98
|
+
if (!entry || typeof entry.token !== 'string')
|
|
63
99
|
return null;
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
100
|
+
const found = slots(entry, now);
|
|
101
|
+
const match = found.find((slot) => slot.quote.fingerprint === fingerprint);
|
|
102
|
+
if (match)
|
|
103
|
+
return present(match);
|
|
104
|
+
// A quote minted for a different input must not lend its turn token.
|
|
105
|
+
if (found.length > 0)
|
|
106
|
+
return null;
|
|
107
|
+
if (now - entry.at >= exports.TURN_REUSE_MS)
|
|
108
|
+
return null;
|
|
109
|
+
return { token: entry.token, quote_hash: null, maximum_credits: null };
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* The turn token stored for this exact quote_hash. Null when that quote is
|
|
113
|
+
* absent or its fingerprint is not this input: never hand back another quote's token.
|
|
114
|
+
*/
|
|
115
|
+
async function reusableTurnForQuote(path, baseUrl, apiKey, capabilityId, quoteHash, fingerprint, now = Date.now(), onWarning) {
|
|
116
|
+
const entry = (await load(path, onWarning))[entryKey(baseUrl, apiKey, capabilityId)];
|
|
117
|
+
if (!entry)
|
|
118
|
+
return null;
|
|
119
|
+
const match = slots(entry, now).find((slot) => slot.quote.quote_hash === quoteHash);
|
|
120
|
+
if (!match || match.quote.fingerprint !== fingerprint)
|
|
121
|
+
return null;
|
|
122
|
+
return present(match);
|
|
74
123
|
}
|
|
75
124
|
async function rememberTurnToken(path, baseUrl, apiKey, capabilityId, token, now = Date.now(), onWarning, quote) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
125
|
+
const key = entryKey(baseUrl, apiKey, capabilityId);
|
|
126
|
+
await modifyCache(path, onWarning, (entries) => {
|
|
127
|
+
for (const [entryKeyName, entry] of Object.entries(entries)) {
|
|
128
|
+
if (!entry || now - entry.at >= exports.TURN_REUSE_MS)
|
|
129
|
+
delete entries[entryKeyName];
|
|
130
|
+
}
|
|
131
|
+
const existing = entries[key];
|
|
132
|
+
const quotes = {};
|
|
133
|
+
for (const slot of slots(existing ?? { token, at: now }, now))
|
|
134
|
+
quotes[slot.quote.quote_hash] = slot;
|
|
135
|
+
if (quote?.quote_hash)
|
|
136
|
+
quotes[quote.quote_hash] = { token, at: now, quote };
|
|
137
|
+
entries[key] = {
|
|
138
|
+
token,
|
|
139
|
+
at: now,
|
|
140
|
+
...(quote ? { quote } : existing?.quote ? { quote: existing.quote } : {}),
|
|
141
|
+
...(Object.keys(quotes).length > 0 ? { quotes } : {}),
|
|
142
|
+
};
|
|
143
|
+
return true;
|
|
144
|
+
});
|
|
83
145
|
}
|
|
84
|
-
async function forgetTurnToken(path, baseUrl, apiKey, capabilityId, onWarning) {
|
|
85
|
-
const entries = await load(path, onWarning);
|
|
146
|
+
async function forgetTurnToken(path, baseUrl, apiKey, capabilityId, onWarning, quoteHash) {
|
|
86
147
|
const key = entryKey(baseUrl, apiKey, capabilityId);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
148
|
+
await modifyCache(path, onWarning, (entries) => {
|
|
149
|
+
const entry = entries[key];
|
|
150
|
+
if (!entry)
|
|
151
|
+
return false;
|
|
152
|
+
if (!quoteHash) {
|
|
153
|
+
delete entries[key];
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
const quotes = {};
|
|
157
|
+
for (const slot of slots(entry, Date.now())) {
|
|
158
|
+
if (slot.quote.quote_hash !== quoteHash)
|
|
159
|
+
quotes[slot.quote.quote_hash] = slot;
|
|
160
|
+
}
|
|
161
|
+
const remaining = Object.values(quotes);
|
|
162
|
+
if (remaining.length === 0) {
|
|
163
|
+
delete entries[key];
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
const kept = remaining[remaining.length - 1];
|
|
167
|
+
entries[key] = { token: kept.token, at: kept.at, quote: kept.quote, quotes };
|
|
168
|
+
return true;
|
|
169
|
+
});
|
|
91
170
|
}
|