@qvac/harness 0.8.6 → 0.8.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/CHANGELOG.md +1259 -507
- package/dist-lib/index.d.ts +11 -0
- package/dist-lib/index.js +102 -25
- package/dist-lib/lib/capabilities/completion.d.ts +4 -4
- package/dist-lib/lib/capabilities/completion.js +86 -57
- package/dist-lib/lib/capabilities/tool-selection.d.ts +1 -3
- package/dist-lib/lib/capabilities/tool-selection.js +2 -4
- package/dist-lib/lib/capabilities/tool-turns.d.ts +0 -1
- package/dist-lib/lib/capabilities/tool-turns.js +0 -6
- package/dist-lib/lib/connect.js +2 -0
- package/dist-lib/lib/contract-types.d.ts +2 -0
- package/dist-lib/lib/helpers/tool-call-markup.d.ts +1 -0
- package/dist-lib/lib/helpers/tool-call-markup.js +3 -0
- package/dist-lib/lib/models/context-choice.d.ts +15 -0
- package/dist-lib/lib/models/context-choice.js +55 -0
- package/dist-lib/lib/models/model-pool.d.ts +3 -1
- package/dist-lib/lib/models/model-pool.js +8 -11
- package/dist-lib/lib/models/sdk-faults.d.ts +1 -0
- package/dist-lib/lib/models/sdk-faults.js +6 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.d.ts +23 -0
- package/dist-lib/lib/office/engine/pptx/picture-area.js +84 -0
- package/dist-lib/lib/office/engine/pptx/presentation.d.ts +2 -0
- package/dist-lib/lib/office/engine/pptx/presentation.js +55 -17
- package/dist-lib/lib/office/engine/pptx/types.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.d.ts +1 -0
- package/dist-lib/lib/office/engine/xlsx/workbook.js +34 -3
- package/dist-lib/lib/serve.js +5 -1
- package/dist-lib/lib/spawn.js +4 -10
- package/dist-lib/lib/tools/exec.d.ts +4 -1
- package/dist-lib/lib/tools/exec.js +55 -11
- package/dist-lib/lib/tools/office/ops.js +1 -1
- package/dist-lib/lib/tools/office.js +29 -6
- package/dist-lib/lib/tools/skills/catalog.d.ts +1 -0
- package/dist-lib/lib/tools/skills/catalog.js +2 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.d.ts +1 -0
- package/dist-lib/lib/tools/skills/cli-schema-check.js +3 -0
- package/dist-lib/lib/tools/skills/cli-schema-load.js +10 -0
- package/dist-lib/lib/tools/skills/cli-schema-types.d.ts +2 -0
- package/dist-lib/lib/tools/skills/cli-schema.js +6 -3
- package/dist-lib/lib/tools/skills/frontmatter-parser.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.d.ts +1 -0
- package/dist-lib/lib/tools/skills/manifest-parse.js +12 -0
- package/dist-lib/lib/tools/skills/mention.d.ts +0 -1
- package/dist-lib/lib/tools/skills/mention.js +0 -3
- package/dist-lib/schema/harness.js +2 -1
- package/dist-lib/spec/hrpc/messages.js +10 -5
- package/dist-lib/spec/hyperschema/index.js +10 -5
- package/dist-lib/spec/hyperschema/schema.json +6 -1
- package/dist-lib/spec/hyperschema/types.d.ts +2 -1
- package/package.json +5 -7
- package/worker/harness-sidecar.mjs +2 -1
package/dist-lib/index.d.ts
CHANGED
|
@@ -99,7 +99,9 @@ export interface CompletionContext {
|
|
|
99
99
|
model: string;
|
|
100
100
|
agent?: Agent;
|
|
101
101
|
messages: Message[];
|
|
102
|
+
typedText?: string;
|
|
102
103
|
cacheKey?: string;
|
|
104
|
+
session?: string;
|
|
103
105
|
thinkingDefault?: string;
|
|
104
106
|
toolConfigs?: Record<string, Record<string, unknown>>;
|
|
105
107
|
generationParams?: LlmGenerationParams;
|
|
@@ -189,6 +191,7 @@ export declare class Harness extends ReadyResource {
|
|
|
189
191
|
private _sizeLoads;
|
|
190
192
|
private _capBytes;
|
|
191
193
|
private readonly _reserved;
|
|
194
|
+
private readonly _contexts;
|
|
192
195
|
private readonly _admission;
|
|
193
196
|
private readonly _byHand;
|
|
194
197
|
constructor({ tools, sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats }?: HarnessOptions);
|
|
@@ -290,14 +293,22 @@ export declare class Harness extends ReadyResource {
|
|
|
290
293
|
private _curatedFit;
|
|
291
294
|
private _admitByHand;
|
|
292
295
|
private _replacedBy;
|
|
296
|
+
private _replacedEntries;
|
|
297
|
+
private _alone;
|
|
293
298
|
private _sizeByHand;
|
|
294
299
|
private _loadByHand;
|
|
295
300
|
private _admit;
|
|
296
301
|
private _makeRoomForGenerator;
|
|
297
302
|
private _yieldGenerators;
|
|
298
303
|
private _beforeLoad;
|
|
304
|
+
private _reserveByHand;
|
|
299
305
|
private _reserve;
|
|
300
306
|
private _sizeLoad;
|
|
307
|
+
private _chooseContext;
|
|
308
|
+
private _capRoom;
|
|
309
|
+
private _contextProfile;
|
|
310
|
+
private _trainedContext;
|
|
311
|
+
private _ctxSizeOf;
|
|
301
312
|
private _makeRoom;
|
|
302
313
|
private _capVictims;
|
|
303
314
|
private _victimRank;
|
package/dist-lib/index.js
CHANGED
|
@@ -28,6 +28,8 @@ import { deleteModelFiles, summarizeDelete } from './lib/models/model-delete.js'
|
|
|
28
28
|
import { createEmbedPool, createLoadTracker, createModelPool, createVoicePool, awaitModelCapacity, evictIdle, evictIdleWhere, evictIdleWithin, evictModel, evictModelWithin, isResident, leaseResidentModel, leaseCount, loadedModels, loadingModels, settledEntries, settledModels, modelKey, modelOf, releaseModel, resolveLoadOptions, progressState } from './lib/models/model-pool.js';
|
|
29
29
|
import { memoryCapBytes, nextVictim, VictimRank } from './lib/models/memory-cap.js';
|
|
30
30
|
import { defaultContextSize } from './lib/models/context.js';
|
|
31
|
+
import { ContextChoice } from './lib/models/context-choice.js';
|
|
32
|
+
import { parseGgufHeader, readGgufHeaderBytes } from './lib/models/gguf-header.js';
|
|
31
33
|
import { resolveMainGpu } from './lib/models/gpu-selection.js';
|
|
32
34
|
import { createIdleReaper } from './lib/models/idle-unload.js';
|
|
33
35
|
import { openclCacheDir } from './lib/models/opencl-cache.js';
|
|
@@ -163,8 +165,9 @@ export class Harness extends ReadyResource {
|
|
|
163
165
|
_sizeLoads = false;
|
|
164
166
|
_capBytes = null;
|
|
165
167
|
_reserved = new Map();
|
|
168
|
+
_contexts = new ContextChoice(() => this._contextProfile());
|
|
166
169
|
_admission = new ScopeLock();
|
|
167
|
-
_byHand = new
|
|
170
|
+
_byHand = new Map();
|
|
168
171
|
constructor({ tools = [], sdkLoader, skillsDir, modelsDir, projectionModel, imageModel, imageClamps, musicModel, musicClamps, llmDevice, llmGpuLayers, contextSize, kvCacheK, kvCacheV, embedDevice, embedGpuLayers, embedBatchSize, transcriptionDevice, completionModelCap, credentialStore, platform, gpuStats } = {}) {
|
|
169
172
|
super();
|
|
170
173
|
this._sdk = sdkLoader ?? createSdkLoader();
|
|
@@ -682,7 +685,7 @@ export class Harness extends ReadyResource {
|
|
|
682
685
|
const key = modelKey(model, loadOpts);
|
|
683
686
|
const pool = this._models;
|
|
684
687
|
if (!isResident(pool, key)) {
|
|
685
|
-
await this._makeRoomForCompletion(model, { ctxSize:
|
|
688
|
+
await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
|
|
686
689
|
}
|
|
687
690
|
try {
|
|
688
691
|
const { modelId } = await raceAbort(pool.open(key, model, loadOpts).load(), signal);
|
|
@@ -721,6 +724,7 @@ export class Harness extends ReadyResource {
|
|
|
721
724
|
deviceConfig: this._deviceConfig(),
|
|
722
725
|
loads: this._loads,
|
|
723
726
|
beforeLoad: (load) => this._beforeLoad(load),
|
|
727
|
+
chooseContext: (load, tier) => this._chooseContext(load, tier),
|
|
724
728
|
hold: this._hold
|
|
725
729
|
});
|
|
726
730
|
}
|
|
@@ -1346,7 +1350,9 @@ export class Harness extends ReadyResource {
|
|
|
1346
1350
|
}
|
|
1347
1351
|
async _admitByHand(model, target) {
|
|
1348
1352
|
if (this._capBytes === null) {
|
|
1349
|
-
return target.pool !== this._models ||
|
|
1353
|
+
return (target.pool !== this._models ||
|
|
1354
|
+
this._alone(model, target) ||
|
|
1355
|
+
this._fits(model, target.ctxSize));
|
|
1350
1356
|
}
|
|
1351
1357
|
return this._admit(async () => {
|
|
1352
1358
|
if (isResident(target.pool, target.key) || this._byHand.has(target.key)) {
|
|
@@ -1354,18 +1360,14 @@ export class Harness extends ReadyResource {
|
|
|
1354
1360
|
}
|
|
1355
1361
|
const sized = await this._sizeByHand(model, target);
|
|
1356
1362
|
const capBytes = this._capBytes;
|
|
1357
|
-
const
|
|
1358
|
-
const replacedBy = this._replacedBy(model);
|
|
1359
|
-
const replaced = target.pool === this._models
|
|
1360
|
-
? settledEntries(this._models).filter(({ key, refs }) => refs === 0 && replacedBy(key))
|
|
1361
|
-
: [];
|
|
1363
|
+
const replaced = this._replacedEntries(model, target);
|
|
1362
1364
|
const used = this._usedBytes() - replaced.reduce((sum, { bytes }) => sum + (bytes ?? 0), 0);
|
|
1363
|
-
const alone = this.
|
|
1365
|
+
const alone = this._alone(model, target);
|
|
1364
1366
|
const fits = capBytes === null || alone || sized.bytes === undefined || used + sized.bytes <= capBytes;
|
|
1365
|
-
if (sized.tooLarge || !fits) {
|
|
1367
|
+
if ((sized.tooLarge && !alone) || !fits) {
|
|
1366
1368
|
return false;
|
|
1367
1369
|
}
|
|
1368
|
-
this._byHand.
|
|
1370
|
+
this._byHand.set(target.key, sized.ctxSize);
|
|
1369
1371
|
if (sized.bytes !== undefined) {
|
|
1370
1372
|
this._reserved.set(target.key, sized.bytes);
|
|
1371
1373
|
}
|
|
@@ -1375,19 +1377,35 @@ export class Harness extends ReadyResource {
|
|
|
1375
1377
|
_replacedBy(model) {
|
|
1376
1378
|
return (key) => modelOf(key) === model && !this._replying.includes(key);
|
|
1377
1379
|
}
|
|
1378
|
-
|
|
1380
|
+
_replacedEntries(model, target) {
|
|
1381
|
+
const replacedBy = this._replacedBy(model);
|
|
1382
|
+
return target.pool === this._models
|
|
1383
|
+
? settledEntries(this._models).filter(({ key, refs }) => refs === 0 && replacedBy(key))
|
|
1384
|
+
: [];
|
|
1385
|
+
}
|
|
1386
|
+
_alone(model, target) {
|
|
1387
|
+
const settled = this._pools().flatMap((pool) => settledEntries(pool));
|
|
1388
|
+
const loading = this._pools().some((pool) => loadingModels(pool).length > 0);
|
|
1389
|
+
return (!loading &&
|
|
1390
|
+
this._reserved.size === 0 &&
|
|
1391
|
+
settled.length === this._replacedEntries(model, target).length);
|
|
1392
|
+
}
|
|
1393
|
+
async _sizeByHand(model, target) {
|
|
1379
1394
|
const addon = modelAddonOf(model);
|
|
1380
1395
|
const modelSrc = curatedModelAsset(model, addon) ?? customModelSrc(model, addon) ?? model;
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1396
|
+
const ctxSize = target.pool === this._models
|
|
1397
|
+
? target.ctxSize || defaultContextSize(modelSrc, customContextLength(model))
|
|
1398
|
+
: undefined;
|
|
1399
|
+
const sized = await this._sizeLoad({
|
|
1383
1400
|
key: target.key,
|
|
1384
1401
|
model,
|
|
1385
1402
|
modelSrc,
|
|
1386
1403
|
modelType: target.modelType,
|
|
1387
|
-
modelConfig:
|
|
1388
|
-
?
|
|
1389
|
-
:
|
|
1404
|
+
modelConfig: ctxSize === undefined
|
|
1405
|
+
? undefined
|
|
1406
|
+
: { ctx_size: ctxSize, projectionModelSrc: this._projectionFor(model) }
|
|
1390
1407
|
});
|
|
1408
|
+
return { ...sized, ctxSize };
|
|
1391
1409
|
}
|
|
1392
1410
|
async _loadByHand(target, signal) {
|
|
1393
1411
|
const loading = target.open().load();
|
|
@@ -1429,11 +1447,22 @@ export class Harness extends ReadyResource {
|
|
|
1429
1447
|
async _beforeLoad(load) {
|
|
1430
1448
|
if (!this._sizeLoads)
|
|
1431
1449
|
return undefined;
|
|
1432
|
-
const bytes = this._byHand.
|
|
1433
|
-
? this.
|
|
1450
|
+
const bytes = this._byHand.has(load.key)
|
|
1451
|
+
? await this._admit(() => this._reserveByHand(load))
|
|
1434
1452
|
: await this._admit(() => this._reserve(load));
|
|
1435
1453
|
return { bytes, release: () => this._reserved.delete(load.key) };
|
|
1436
1454
|
}
|
|
1455
|
+
async _reserveByHand(load) {
|
|
1456
|
+
const admittedAt = this._byHand.get(load.key);
|
|
1457
|
+
this._byHand.delete(load.key);
|
|
1458
|
+
if (load.modelConfig?.['ctx_size'] !== admittedAt) {
|
|
1459
|
+
const { bytes } = await this._sizeLoad(load);
|
|
1460
|
+
if (bytes !== undefined) {
|
|
1461
|
+
this._reserved.set(load.key, bytes);
|
|
1462
|
+
}
|
|
1463
|
+
}
|
|
1464
|
+
return this._reserved.get(load.key);
|
|
1465
|
+
}
|
|
1437
1466
|
async _reserve(load) {
|
|
1438
1467
|
const sized = await this._sizeLoad(load);
|
|
1439
1468
|
if (this._capBytes !== null) {
|
|
@@ -1461,17 +1490,60 @@ export class Harness extends ReadyResource {
|
|
|
1461
1490
|
const bytes = fit?.estimate?.upperBoundBytes ?? fit?.floorBytes ?? downloaded;
|
|
1462
1491
|
return {
|
|
1463
1492
|
bytes: bytes === undefined ? undefined : bytes + projection,
|
|
1464
|
-
tooLarge: fit?.verdict === 'likely-too-large'
|
|
1493
|
+
tooLarge: fit?.verdict === 'likely-too-large',
|
|
1494
|
+
confirmed: fit?.verdict === 'likely-fits' &&
|
|
1495
|
+
(fit.evidence === 'calibration' || fit.evidence === 'native-fit')
|
|
1465
1496
|
};
|
|
1466
1497
|
}
|
|
1467
1498
|
catch (err) {
|
|
1468
1499
|
hlog('fit estimate failed', { model, err: errorMessage(err) });
|
|
1469
1500
|
return {
|
|
1470
1501
|
bytes: downloaded === undefined ? undefined : downloaded + projection,
|
|
1471
|
-
tooLarge: false
|
|
1502
|
+
tooLarge: false,
|
|
1503
|
+
confirmed: false
|
|
1472
1504
|
};
|
|
1473
1505
|
}
|
|
1474
1506
|
}
|
|
1507
|
+
async _chooseContext(load, tier) {
|
|
1508
|
+
if (isTranslationModel(load.model)) {
|
|
1509
|
+
return tier;
|
|
1510
|
+
}
|
|
1511
|
+
const ctxSize = await this._contexts.choose(load.key, tier, {
|
|
1512
|
+
trained: () => this._trainedContext(load),
|
|
1513
|
+
fits: async (rung) => {
|
|
1514
|
+
const sized = await this._sizeLoad({
|
|
1515
|
+
...load,
|
|
1516
|
+
modelConfig: { ...load.modelConfig, ctx_size: rung }
|
|
1517
|
+
});
|
|
1518
|
+
const room = this._capRoom(load.key);
|
|
1519
|
+
return sized.confirmed && (room === null || (sized.bytes ?? Infinity) <= room);
|
|
1520
|
+
}
|
|
1521
|
+
});
|
|
1522
|
+
hlog('context chosen', { model: load.model, tier, ctxSize });
|
|
1523
|
+
return ctxSize;
|
|
1524
|
+
}
|
|
1525
|
+
_capRoom(key) {
|
|
1526
|
+
const cap = this._capBytes;
|
|
1527
|
+
return cap === null ? null : cap - (this._usedBytes() - (this._reserved.get(key) ?? 0));
|
|
1528
|
+
}
|
|
1529
|
+
async _contextProfile() {
|
|
1530
|
+
const { capabilityProfile, memoryBudget } = await this.capability();
|
|
1531
|
+
return memoryBudget ? capabilityProfile : undefined;
|
|
1532
|
+
}
|
|
1533
|
+
async _trainedContext({ model, modelSrc }) {
|
|
1534
|
+
const custom = customContextLength(model);
|
|
1535
|
+
if (custom || typeof modelSrc === 'string') {
|
|
1536
|
+
return custom;
|
|
1537
|
+
}
|
|
1538
|
+
const sdk = await this._sdk.load();
|
|
1539
|
+
const info = await sdk.getModelInfo({ name: modelSrc.name }).catch(() => null);
|
|
1540
|
+
const file = info?.cacheFiles[0]?.path;
|
|
1541
|
+
const bytes = file ? await readGgufHeaderBytes(file) : null;
|
|
1542
|
+
return (bytes && parseGgufHeader(bytes)?.contextLength) || undefined;
|
|
1543
|
+
}
|
|
1544
|
+
_ctxSizeOf(key, loadOpts) {
|
|
1545
|
+
return loadOpts.ctxSize || this._contexts.chosen(key);
|
|
1546
|
+
}
|
|
1475
1547
|
async _makeRoom(need, room) {
|
|
1476
1548
|
const capBytes = this._capBytes;
|
|
1477
1549
|
if (capBytes === null) {
|
|
@@ -1563,7 +1635,7 @@ export class Harness extends ReadyResource {
|
|
|
1563
1635
|
pool: this._models,
|
|
1564
1636
|
key,
|
|
1565
1637
|
modelType: 'llamacpp-completion',
|
|
1566
|
-
ctxSize:
|
|
1638
|
+
ctxSize: this._ctxSizeOf(key, loadOpts),
|
|
1567
1639
|
open: () => this._models.open(key, model, loadOpts)
|
|
1568
1640
|
};
|
|
1569
1641
|
}
|
|
@@ -1625,7 +1697,7 @@ export class Harness extends ReadyResource {
|
|
|
1625
1697
|
completion(context) {
|
|
1626
1698
|
return this._busyStream(() => this._completion(context));
|
|
1627
1699
|
}
|
|
1628
|
-
async *_completion({ model, agent, messages, cacheKey, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
1700
|
+
async *_completion({ model, agent, messages, typedText, cacheKey, session, thinkingDefault, toolConfigs, generationParams, responseFormat, captureInferenceCalls, grantedDirs, grantedDrives, driveOp, grantedWriteDirs, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal }) {
|
|
1629
1701
|
if (!this.opened)
|
|
1630
1702
|
await this.ready();
|
|
1631
1703
|
if (this.suspended)
|
|
@@ -1637,12 +1709,15 @@ export class Harness extends ReadyResource {
|
|
|
1637
1709
|
const resident = isResident(pool, key);
|
|
1638
1710
|
hlog('completion start', { model, resident, projection: Boolean(projectionModel) });
|
|
1639
1711
|
if (!resident) {
|
|
1640
|
-
await this._makeRoomForCompletion(model, { ctxSize:
|
|
1712
|
+
await this._makeRoomForCompletion(model, { ctxSize: this._ctxSizeOf(key, loadOpts), signal });
|
|
1641
1713
|
await this._yieldGenerators();
|
|
1642
1714
|
}
|
|
1643
1715
|
const acquireRound = async () => {
|
|
1644
1716
|
if (!isResident(pool, key)) {
|
|
1645
|
-
await this._makeRoomForCompletion(model, {
|
|
1717
|
+
await this._makeRoomForCompletion(model, {
|
|
1718
|
+
ctxSize: this._ctxSizeOf(key, loadOpts),
|
|
1719
|
+
signal
|
|
1720
|
+
});
|
|
1646
1721
|
await this._yieldGenerators();
|
|
1647
1722
|
}
|
|
1648
1723
|
try {
|
|
@@ -1690,11 +1765,13 @@ export class Harness extends ReadyResource {
|
|
|
1690
1765
|
agent,
|
|
1691
1766
|
ctxSize,
|
|
1692
1767
|
cacheKey,
|
|
1768
|
+
session,
|
|
1693
1769
|
thinkingDefault,
|
|
1694
1770
|
generationParams,
|
|
1695
1771
|
responseFormat,
|
|
1696
1772
|
captureInferenceCalls,
|
|
1697
1773
|
messages: turns,
|
|
1774
|
+
typedText,
|
|
1698
1775
|
tools: this._tools,
|
|
1699
1776
|
skillCatalog: await this._completionSkills(agent?.id ?? null, model),
|
|
1700
1777
|
toolConfigs,
|
|
@@ -125,6 +125,7 @@ export interface ToolExecuteContext {
|
|
|
125
125
|
skills?: readonly string[] | undefined;
|
|
126
126
|
skillsLoadedThisTurn?: readonly string[] | undefined;
|
|
127
127
|
ctxSize?: number | undefined;
|
|
128
|
+
session?: string | undefined;
|
|
128
129
|
image?: ImageSettings | undefined;
|
|
129
130
|
settings?: Record<string, unknown> | undefined;
|
|
130
131
|
turnImages?: {
|
|
@@ -296,9 +297,6 @@ export interface CompletionSdk {
|
|
|
296
297
|
cancel?(params: {
|
|
297
298
|
requestId: string;
|
|
298
299
|
}): Promise<void>;
|
|
299
|
-
deleteCache?(params: {
|
|
300
|
-
kvCacheKey: string;
|
|
301
|
-
}): Promise<unknown>;
|
|
302
300
|
}
|
|
303
301
|
export interface RoundModelLease {
|
|
304
302
|
modelId: string;
|
|
@@ -311,10 +309,12 @@ interface CompletionArgs {
|
|
|
311
309
|
agent?: Agent;
|
|
312
310
|
ctxSize?: number;
|
|
313
311
|
cacheKey?: string;
|
|
312
|
+
session?: string;
|
|
314
313
|
thinkingDefault?: string;
|
|
315
314
|
generationParams?: LlmGenerationParams;
|
|
316
315
|
responseFormat?: ResponseFormat;
|
|
317
316
|
messages: Message[];
|
|
317
|
+
typedText?: string;
|
|
318
318
|
tools?: ToolDefinition[];
|
|
319
319
|
skillCatalog?: readonly CompletionSkillEntry[];
|
|
320
320
|
approve?: ApproveFn;
|
|
@@ -335,5 +335,5 @@ export declare function toSdkTool(tool: ToolDefinition): SdkTool;
|
|
|
335
335
|
export type CompletionSkillEntry = SkillCatalogEntry & {
|
|
336
336
|
readonly status?: SkillState;
|
|
337
337
|
};
|
|
338
|
-
export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
|
|
338
|
+
export declare function completion(sdk: CompletionSdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, session, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, typedText, tools: allTools, skillCatalog, approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots, fsWriteRoots, captureInferenceCalls }: CompletionArgs): AsyncGenerator<CompletionEvent>;
|
|
339
339
|
export {};
|
|
@@ -8,14 +8,14 @@ import { AbortedError, raceAbort } from '../helpers/race-abort.js';
|
|
|
8
8
|
import { raceTimeout } from '../helpers/race-timeout.js';
|
|
9
9
|
import { createRunOutcomeTracker, degenerateAnswer, unnamedWrites, writeReport } from '../helpers/run-outcome.js';
|
|
10
10
|
import { splitThinking } from '../helpers/thinking.js';
|
|
11
|
-
import { clipRawCall, normalizeToolArgs, recoverMarkupToolCalls, recoverPrintedSkillLoad, recoverSkillNamedCall, stripToolCallMarkup, stripToolResponseBlocks, toolCallMarkup } from '../helpers/tool-call-markup.js';
|
|
11
|
+
import { clipRawCall, hasMarkupCall, normalizeToolArgs, recoverMarkupToolCalls, recoverPrintedSkillLoad, recoverSkillNamedCall, stripToolCallMarkup, stripToolResponseBlocks, toolCallMarkup } from '../helpers/tool-call-markup.js';
|
|
12
12
|
import { INTENT_PARAM, INTENT_PARAM_DESCRIPTION, takeToolIntent } from '../helpers/tool-intent.js';
|
|
13
13
|
import { HookPipeline } from '../hooks/pipeline.js';
|
|
14
14
|
import { fallbackToolLabel } from '../helpers/tool-label.js';
|
|
15
15
|
import { hlog } from '../log.js';
|
|
16
16
|
import { resolveLlmGenerationParams } from '../models/llm-presets.js';
|
|
17
17
|
import { toolDialectFor } from '../models/model-catalog.js';
|
|
18
|
-
import { isContextOverflowError, isSdkModelWedgedError } from '../models/sdk-faults.js';
|
|
18
|
+
import { isContextOverflowError, isGrammarOverrunError, isSdkModelWedgedError } from '../models/sdk-faults.js';
|
|
19
19
|
import { annotateLastUserTime, skillsPromptSection, systemPromptParts } from '../prompt.js';
|
|
20
20
|
import { isMetadataMethod } from '../tools/mcp/protocol.js';
|
|
21
21
|
import { unquoteArg } from '../tools/model-args.js';
|
|
@@ -25,11 +25,11 @@ import { promptUsageParts, usageBreakdown } from './usage-breakdown.js';
|
|
|
25
25
|
import { collectToolGrants, enabledTools, isSelected, skillIsActive, skillsAreSelected } from './tool-selection.js';
|
|
26
26
|
import { guardToolCall } from '../tools/outbound-guard.js';
|
|
27
27
|
import { isAvailableState } from '../tools/skills/skill-state-constants.js';
|
|
28
|
-
import { resolveImplicitSkill, resolveSkillMention
|
|
28
|
+
import { resolveImplicitSkill, resolveSkillMention } from '../tools/skills/mention.js';
|
|
29
29
|
import { NETWORK_TOOLSETS, permittedTools } from '../tools/toolsets.js';
|
|
30
30
|
import { promptPrefixHash } from './prompt-prefix.js';
|
|
31
31
|
import { logToolPrompt } from './tool-prompt-log.js';
|
|
32
|
-
import { DIRECT_CALL_ID, deliversFile, FILE_DELIVERED_NUDGE, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn
|
|
32
|
+
import { DIRECT_CALL_ID, deliversFile, FILE_DELIVERED_NUDGE, loadedSkillFilesFrom, loadedSkillsFrom, TOOL_CONTINUATION_NUDGE, toolResultTurn } from './tool-turns.js';
|
|
33
33
|
export function imageSettingsFrom(agent) {
|
|
34
34
|
if (!agent)
|
|
35
35
|
return undefined;
|
|
@@ -139,14 +139,11 @@ function explicitlyDisablesWebSearch(text) {
|
|
|
139
139
|
return (/\b(?:do\s+not|don't|dont|never)\s+(?:search|browse|look\s+up|find|use)\s+(?:the\s+)?(?:web|internet|online)\b/i.test(text) ||
|
|
140
140
|
/\b(?:avoid|without)\s+(?:searching|browsing|using)\s+(?:the\s+)?(?:web|internet|online)\b/i.test(text));
|
|
141
141
|
}
|
|
142
|
-
function directToolHistory(history,
|
|
142
|
+
function directToolHistory(history, call, output) {
|
|
143
143
|
return [
|
|
144
144
|
...history,
|
|
145
|
-
{
|
|
146
|
-
|
|
147
|
-
content: toolCallMarkup([{ name: WEB_SEARCH_TOOL_NAME, arguments: args }])
|
|
148
|
-
},
|
|
149
|
-
{ role: 'tool', content: toolResultTurn(WEB_SEARCH_TOOL_NAME, DIRECT_CALL_ID, output) }
|
|
145
|
+
{ role: 'assistant', content: toolCallMarkup([call]) },
|
|
146
|
+
{ role: 'tool', content: toolResultTurn(call.name, DIRECT_CALL_ID, output) }
|
|
150
147
|
];
|
|
151
148
|
}
|
|
152
149
|
function assistantToolTurn(contentText, calls) {
|
|
@@ -155,18 +152,6 @@ function assistantToolTurn(contentText, calls) {
|
|
|
155
152
|
const markup = toolCallMarkup(calls);
|
|
156
153
|
return contentText ? `${contentText}\n\n${markup}` : markup;
|
|
157
154
|
}
|
|
158
|
-
function preloadLastUser(messages, name, body) {
|
|
159
|
-
for (let i = messages.length - 1; i >= 0; i--) {
|
|
160
|
-
const turn = messages[i];
|
|
161
|
-
if (turn?.role !== 'user') {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
const next = [...messages];
|
|
165
|
-
next[i] = { ...turn, content: skillPreloadMessage(turn.content, name, body) };
|
|
166
|
-
return next;
|
|
167
|
-
}
|
|
168
|
-
return messages;
|
|
169
|
-
}
|
|
170
155
|
function collectStats(state, stats, stopReason) {
|
|
171
156
|
state.stopReason = stopReason;
|
|
172
157
|
if (!stats) {
|
|
@@ -362,10 +347,11 @@ async function executeCall(tool, args, { ctx, approve, signal, denied, sources =
|
|
|
362
347
|
return executedCall(output, outcome);
|
|
363
348
|
}
|
|
364
349
|
function runToolContext(scope) {
|
|
365
|
-
const { agent, attach, fetchAttachment, ragSearch, progress, askUser, signal, runBudget } = scope;
|
|
350
|
+
const { agent, session, attach, fetchAttachment, ragSearch, progress, askUser, signal, runBudget } = scope;
|
|
366
351
|
const image = imageSettingsFrom(agent);
|
|
367
352
|
return (turnImages = [], ctxSize) => ({
|
|
368
353
|
agentId: agent?.id ?? null,
|
|
354
|
+
session,
|
|
369
355
|
ctxSize,
|
|
370
356
|
image,
|
|
371
357
|
turnImages: turnImages.length ? turnImages : undefined,
|
|
@@ -466,6 +452,20 @@ async function* contentDeltas(events) {
|
|
|
466
452
|
}
|
|
467
453
|
}
|
|
468
454
|
}
|
|
455
|
+
async function* endAtGrammarOverrun(tokens, state) {
|
|
456
|
+
try {
|
|
457
|
+
for await (const token of tokens) {
|
|
458
|
+
state.rawText += token;
|
|
459
|
+
yield token;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
catch (err) {
|
|
463
|
+
if (!isGrammarOverrunError(err)) {
|
|
464
|
+
throw err;
|
|
465
|
+
}
|
|
466
|
+
state.overrun = err;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
469
|
async function* runRoundTokens(result, state, { sdk, signal, startInThink = false }) {
|
|
470
470
|
const cancelThisRound = () => {
|
|
471
471
|
if (result.requestId) {
|
|
@@ -473,7 +473,8 @@ async function* runRoundTokens(result, state, { sdk, signal, startInThink = fals
|
|
|
473
473
|
}
|
|
474
474
|
};
|
|
475
475
|
signal?.addEventListener('abort', cancelThisRound, { once: true });
|
|
476
|
-
const
|
|
476
|
+
const deltas = endAtGrammarOverrun(contentDeltas(result.events), state);
|
|
477
|
+
const tokens = stopAtControlToken(deltas, (token) => {
|
|
477
478
|
state.stopToken = token;
|
|
478
479
|
});
|
|
479
480
|
let tokenEvents = 0;
|
|
@@ -508,6 +509,9 @@ async function* runRoundTokens(result, state, { sdk, signal, startInThink = fals
|
|
|
508
509
|
if (contentTail) {
|
|
509
510
|
yield { type: 'content', text: contentTail };
|
|
510
511
|
}
|
|
512
|
+
if (state.overrun) {
|
|
513
|
+
throw state.overrun;
|
|
514
|
+
}
|
|
511
515
|
hlog('stream ended', { events: tokenEvents, stopToken: state.stopToken });
|
|
512
516
|
if (state.stopToken) {
|
|
513
517
|
cancelThisRound();
|
|
@@ -533,9 +537,12 @@ function roundTokens(stats) {
|
|
|
533
537
|
};
|
|
534
538
|
return Object.values(tokens).some((value) => value !== undefined) ? tokens : undefined;
|
|
535
539
|
}
|
|
540
|
+
function acceptedFinal(contentText, fullText = contentText) {
|
|
541
|
+
return { contentText, toolCalls: [], raw: { fullText } };
|
|
542
|
+
}
|
|
536
543
|
const TRUNCATED_FINAL_MS = 2000;
|
|
537
544
|
function truncatedFinal(final, contentText) {
|
|
538
|
-
const accepted =
|
|
545
|
+
const accepted = acceptedFinal(contentText);
|
|
539
546
|
const guarded = final.catch((err) => {
|
|
540
547
|
if (isSdkModelWedgedError(err))
|
|
541
548
|
throw err;
|
|
@@ -560,7 +567,13 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
|
|
|
560
567
|
throw new AbortedError();
|
|
561
568
|
let contentText = '';
|
|
562
569
|
let emitted = false;
|
|
563
|
-
const state = {
|
|
570
|
+
const state = {
|
|
571
|
+
stopToken: null,
|
|
572
|
+
unproven: null,
|
|
573
|
+
rawContentText: '',
|
|
574
|
+
rawText: '',
|
|
575
|
+
overrun: null
|
|
576
|
+
};
|
|
564
577
|
const cacheable = candidate.history === args.history;
|
|
565
578
|
const params = {
|
|
566
579
|
modelId,
|
|
@@ -633,6 +646,14 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
|
|
|
633
646
|
};
|
|
634
647
|
}
|
|
635
648
|
catch (err) {
|
|
649
|
+
const recovered = isGrammarOverrunError(err) && hasMarkupCall(state.rawText);
|
|
650
|
+
let outcome = 'failed';
|
|
651
|
+
if (recovered) {
|
|
652
|
+
outcome = 'completed';
|
|
653
|
+
}
|
|
654
|
+
else if (isContextOverflowError(err)) {
|
|
655
|
+
outcome = 'overflowed';
|
|
656
|
+
}
|
|
636
657
|
if (args.capture) {
|
|
637
658
|
yield {
|
|
638
659
|
type: 'inference-sent',
|
|
@@ -640,10 +661,21 @@ async function* runRecoveredRoundWith(sdk, args, modelId) {
|
|
|
640
661
|
droppedTurns: candidate.droppedTurns,
|
|
641
662
|
messages: undefined,
|
|
642
663
|
tokens: undefined,
|
|
643
|
-
outcome
|
|
664
|
+
outcome,
|
|
644
665
|
reason: errorMessage(err)
|
|
645
666
|
};
|
|
646
667
|
}
|
|
668
|
+
if (recovered) {
|
|
669
|
+
hlog('grammar overrun recovered', { round: args.round });
|
|
670
|
+
return {
|
|
671
|
+
contentText,
|
|
672
|
+
final: acceptedFinal(state.rawContentText, state.rawText),
|
|
673
|
+
history: candidate.history,
|
|
674
|
+
cacheable,
|
|
675
|
+
unproven: state.unproven,
|
|
676
|
+
replay: null
|
|
677
|
+
};
|
|
678
|
+
}
|
|
647
679
|
if (!isContextOverflowError(err)) {
|
|
648
680
|
throw err;
|
|
649
681
|
}
|
|
@@ -664,7 +696,7 @@ function* reportUnnamedWrites(said, landed) {
|
|
|
664
696
|
yield { type: 'content', text: unreported };
|
|
665
697
|
}
|
|
666
698
|
}
|
|
667
|
-
export async function* completion(sdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, tools: allTools = [], skillCatalog = [], approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots = [], fsWriteRoots = [], captureInferenceCalls = false }) {
|
|
699
|
+
export async function* completion(sdk, { modelId, acquireRound, model, agent, ctxSize, cacheKey, session, thinkingDefault, generationParams: generationParamsOverride, responseFormat, messages: storedMessages, typedText, tools: allTools = [], skillCatalog = [], approve, attach, fetchAttachment, ragSearch, progress, askUser, signal, toolConfigs, fsRoots = [], fsWriteRoots = [], captureInferenceCalls = false }) {
|
|
668
700
|
const pipeline = new HookPipeline();
|
|
669
701
|
const agentId = agent?.id ?? null;
|
|
670
702
|
const disabledToolsets = agent?.sandboxMode
|
|
@@ -676,12 +708,12 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
676
708
|
...fsRoots.filter((root) => root.kind === 'hyper' && root.writable && !fsWriteRoots.includes(root))
|
|
677
709
|
];
|
|
678
710
|
const messages = storedMessages.map(sanitizeTurn);
|
|
711
|
+
const userText = typedText === undefined ? lastUserContent(messages) : stripControlTokens(typedText);
|
|
679
712
|
const stats = { tokens: 0, timedTokens: 0, durationMs: 0, ctxSize, backends: [] };
|
|
680
713
|
let runOutcome = 'abandoned';
|
|
681
714
|
let runError;
|
|
682
715
|
const runOutcomes = createRunOutcomeTracker();
|
|
683
716
|
const landed = runOutcomes.confirmedWrites;
|
|
684
|
-
let preloadCacheKey;
|
|
685
717
|
async function* runTurn() {
|
|
686
718
|
if (signal?.aborted)
|
|
687
719
|
throw new AbortedError();
|
|
@@ -692,6 +724,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
692
724
|
};
|
|
693
725
|
const toolContext = runToolContext({
|
|
694
726
|
agent,
|
|
727
|
+
session: session ?? sessionOf(cacheKey),
|
|
695
728
|
attach,
|
|
696
729
|
fetchAttachment,
|
|
697
730
|
ragSearch,
|
|
@@ -704,16 +737,14 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
704
737
|
const toolsSelected = isSelected(agent?.toolsScope, agent?.enabledTools);
|
|
705
738
|
const skillsSelected = skillsAreSelected(agent?.skillsScope, Boolean(agent));
|
|
706
739
|
const inContextSkills = skillCatalog.filter(skillInContext);
|
|
707
|
-
const mention = resolveSkillMention(lastUserContent(messages), inContextSkills);
|
|
708
740
|
const loadedSkills = loadedSkillsFrom(messages);
|
|
709
741
|
const loadedSkillFiles = loadedSkillFilesFrom(messages);
|
|
710
742
|
const activation = {
|
|
711
743
|
skillsSelected,
|
|
712
|
-
enabledSkills: agent?.enabledSkills ?? []
|
|
713
|
-
mention: mention?.name ?? null,
|
|
714
|
-
loadedSkills
|
|
744
|
+
enabledSkills: agent?.enabledSkills ?? []
|
|
715
745
|
};
|
|
716
746
|
const activeSkills = inContextSkills.filter((skill) => skillIsActive(skill.name, activation));
|
|
747
|
+
const mention = resolveSkillMention(userText, activeSkills);
|
|
717
748
|
const { grantedNames, grantedOwners, grantedOps } = collectToolGrants(activeSkills);
|
|
718
749
|
function selectEnabled() {
|
|
719
750
|
if (responseFormat)
|
|
@@ -762,16 +793,15 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
762
793
|
skillIcon = skillEmoji(skillName) ?? skillIcon;
|
|
763
794
|
}
|
|
764
795
|
const toolDialect = model ? toolDialectFor(model) : undefined;
|
|
765
|
-
let turns = messages;
|
|
766
796
|
let preloadedSkill = null;
|
|
767
|
-
|
|
797
|
+
let preloadedResult = null;
|
|
798
|
+
const preload = mention ?? resolveImplicitSkill(userText, activeSkills);
|
|
768
799
|
if (preload) {
|
|
769
800
|
const loader = tools.find((tool) => tool.name === SKILL_TOOL_NAME);
|
|
770
801
|
const loaded = loader ? await loader.execute({ name: preload.name }, toolContext()) : null;
|
|
771
802
|
if (loaded && typeof loaded === 'object' && 'content' in loaded) {
|
|
772
803
|
preloadedSkill = String(loaded.content);
|
|
773
|
-
|
|
774
|
-
turns = withoutSkillLoadTurns(inlined, preload.name);
|
|
804
|
+
preloadedResult = loaded;
|
|
775
805
|
if (!loadedSkills.has(preload.name)) {
|
|
776
806
|
const preloadArgs = { name: preload.name };
|
|
777
807
|
const preloadEffect = 'read';
|
|
@@ -799,6 +829,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
799
829
|
effect: preloadEffect,
|
|
800
830
|
resource: preloadResource
|
|
801
831
|
});
|
|
832
|
+
preloadedResult = settled.result;
|
|
802
833
|
yield {
|
|
803
834
|
type: 'tool-result',
|
|
804
835
|
name: SKILL_TOOL_NAME,
|
|
@@ -841,19 +872,19 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
841
872
|
});
|
|
842
873
|
}
|
|
843
874
|
logSelection();
|
|
844
|
-
let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...
|
|
845
|
-
|
|
846
|
-
|
|
875
|
+
let history = annotateLastUserTime(systemContent ? [{ role: 'system', content: systemContent }, ...messages] : messages);
|
|
876
|
+
if (preload && preloadedSkill !== null) {
|
|
877
|
+
history = directToolHistory(history, { name: SKILL_TOOL_NAME, arguments: { name: preload.name } }, preloadedResult);
|
|
878
|
+
history.push({ role: 'user', content: TOOL_CONTINUATION_NUDGE });
|
|
879
|
+
}
|
|
880
|
+
let roundCacheKey = cacheKey;
|
|
847
881
|
const runUsage = () => usageBreakdown(promptParts, stats.cacheTokens, model);
|
|
848
882
|
const denied = new Set();
|
|
849
883
|
const failed = new Map();
|
|
850
884
|
let blockedRepeats = 0;
|
|
851
885
|
let webSearches = 0;
|
|
852
|
-
const sources = trustedSourcesFrom(
|
|
853
|
-
|
|
854
|
-
sources.push({ origin: 'skill', text: preloadedSkill });
|
|
855
|
-
}
|
|
856
|
-
const query = explicitWebSearchQuery(lastUserContent(messages), enabled, {
|
|
886
|
+
const sources = trustedSourcesFrom(history);
|
|
887
|
+
const query = explicitWebSearchQuery(userText, enabled, {
|
|
857
888
|
skillPreloaded: preloadedSkill !== null
|
|
858
889
|
});
|
|
859
890
|
const webSearch = enabled.find((tool) => tool.name === WEB_SEARCH_TOOL_NAME);
|
|
@@ -926,7 +957,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
926
957
|
name: webSearch.name,
|
|
927
958
|
result: output
|
|
928
959
|
};
|
|
929
|
-
history = directToolHistory(history, args, output);
|
|
960
|
+
history = directToolHistory(history, { name: webSearch.name, arguments: args }, output);
|
|
930
961
|
}
|
|
931
962
|
let outcome = 'exhausted';
|
|
932
963
|
let said = '';
|
|
@@ -1030,6 +1061,11 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1030
1061
|
break;
|
|
1031
1062
|
}
|
|
1032
1063
|
hlog('round produced no answer', { round, mute });
|
|
1064
|
+
history = [
|
|
1065
|
+
...history,
|
|
1066
|
+
{ role: 'assistant', content: spoken },
|
|
1067
|
+
{ role: 'user', content: TOOL_CONTINUATION_NUDGE }
|
|
1068
|
+
];
|
|
1033
1069
|
if (mute++ < MAX_MUTE_ROUNDS) {
|
|
1034
1070
|
continue;
|
|
1035
1071
|
}
|
|
@@ -1067,9 +1103,7 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1067
1103
|
const searchCapped = tool?.name === WEB_SEARCH_TOOL_NAME && webSearches >= MAX_WEB_SEARCHES_PER_TURN;
|
|
1068
1104
|
const callCtx = {
|
|
1069
1105
|
...toolContext(turnImages, ctxSize),
|
|
1070
|
-
skills: [...loadedSkills].filter((name) =>
|
|
1071
|
-
(agent?.enabledSkills ?? []).includes(name) ||
|
|
1072
|
-
name === mention?.name),
|
|
1106
|
+
skills: [...loadedSkills].filter((name) => skillIsActive(name, activation)),
|
|
1073
1107
|
skillsLoadedThisTurn: [...loadedThisTurn],
|
|
1074
1108
|
settings: toolConfigs?.[call.name],
|
|
1075
1109
|
toolOps: grantedOps,
|
|
@@ -1332,16 +1366,11 @@ export async function* completion(sdk, { modelId, acquireRound, model, agent, ct
|
|
|
1332
1366
|
outcome: runOutcome,
|
|
1333
1367
|
error: runError
|
|
1334
1368
|
});
|
|
1335
|
-
if (preloadCacheKey) {
|
|
1336
|
-
await sdk.deleteCache?.({ kvCacheKey: preloadCacheKey }).catch(noop);
|
|
1337
|
-
}
|
|
1338
1369
|
}
|
|
1339
1370
|
}
|
|
1340
|
-
function
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
}
|
|
1344
|
-
return `${cacheKey}/p${turnCount}`;
|
|
1371
|
+
function sessionOf(cacheKey) {
|
|
1372
|
+
const base = cacheKey?.split('/')[0];
|
|
1373
|
+
return base ? base : undefined;
|
|
1345
1374
|
}
|
|
1346
1375
|
function toolDecision({ auto, skipAll, approved }) {
|
|
1347
1376
|
if (auto)
|