mixdog 1.0.2 → 1.0.4
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 +8 -0
- package/package.json +1 -1
- package/src/defaults/skills/browser-use/SKILL.md +14 -6
- package/src/defaults/skills/goal-management/SKILL.md +2 -2
- package/src/runtime/agent/orchestrator/providers/anthropic-midstream-recovery.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-defs.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +40 -99
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +7 -3
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +18 -38
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +59 -32
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-pattern-fanout.mjs +15 -10
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-request.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shared-native-scan.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-source-windows.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/patch/apply-patch/codex-batch.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +17 -9
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/patch/wave.mjs +4 -0
- package/src/runtime/browser-bridge/input-fields.mjs +1 -1
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/computer-bridge/actions.mjs +0 -13
- package/src/runtime/media/adapters/antigravity-image.mjs +7 -3
- package/src/runtime/media/adapters/codex-image.mjs +12 -1
- package/src/runtime/media/adapters/gemini-image.mjs +17 -6
- package/src/runtime/media/adapters/gemini-video.mjs +19 -9
- package/src/runtime/media/adapters/xai-media.mjs +20 -7
- package/src/runtime/media/jobs.mjs +35 -3
- package/src/runtime/media/media-usage.mjs +126 -0
- package/src/runtime/media/tool.mjs +16 -4
- package/src/runtime/shared/llm/cost.mjs +50 -3
- package/src/runtime/shared/llm/model-catalog-projection.mjs +7 -2
- package/src/runtime/shared/llm/model-catalog.mjs +2 -1
- package/src/runtime/shared/llm/model-pricing-rates.mjs +21 -0
- package/src/runtime/shared/llm/usage-accounting.mjs +25 -5
- package/src/runtime/shared/llm/usage-context.mjs +6 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +1 -0
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +9 -1
- package/src/session-runtime/services/agent-tool/index-write-queue.mjs +38 -6
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +9 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +6 -3
- package/src/session-runtime/services/hook-bus/event-runner.mjs +10 -2
- package/src/session-runtime/services/hook-bus/tool-gate.mjs +5 -2
- package/src/tui/dist/index.mjs +7 -3
|
@@ -9,6 +9,7 @@ import { GREP_AUTO_CONTEXT_LINES, trueCasePath } from '../path-utils.mjs';
|
|
|
9
9
|
import { buildGrepRgArgs } from '../search-builders.mjs';
|
|
10
10
|
import { runRgWindowedLines } from '../native-search-runner.mjs';
|
|
11
11
|
import { statReachable } from '../fs-reachability.mjs';
|
|
12
|
+
import { runSharedNativeScan } from './shared-native-scan.mjs';
|
|
12
13
|
import {
|
|
13
14
|
dedupeFanoutMatchLines,
|
|
14
15
|
formatGrepFanoutSections,
|
|
@@ -67,13 +68,15 @@ async function fanoutPrefilterCandidates(request) {
|
|
|
67
68
|
multilineMode,
|
|
68
69
|
withFilename: false,
|
|
69
70
|
});
|
|
70
|
-
const pre = await request.runWindowedLines
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const pre = await runSharedNativeScan(request.runWindowedLines, {
|
|
72
|
+
cwd: scope.cwd,
|
|
73
|
+
argv: prefilterArgs,
|
|
73
74
|
// Whole-scope fallback pass: the broad admission lane keeps it
|
|
74
75
|
// from competing with interactive searches for disk bandwidth.
|
|
75
|
-
{ offset: 0, limit: GREP_FANOUT_PREFILTER_FILE_CAP, summaryLimit: 0, bulkHint: true }
|
|
76
|
-
|
|
76
|
+
window: { offset: 0, limit: GREP_FANOUT_PREFILTER_FILE_CAP, summaryLimit: 0, bulkHint: true },
|
|
77
|
+
scopes: [grepResolvedPath],
|
|
78
|
+
signal: options.signal,
|
|
79
|
+
});
|
|
77
80
|
return pre.complete && !pre.partial ? pre.lines : null;
|
|
78
81
|
} catch {
|
|
79
82
|
options.signal?.throwIfAborted();
|
|
@@ -245,11 +248,13 @@ async function streamCombinedLines(request, scope) {
|
|
|
245
248
|
const combinedCap = Math.min(4000, Math.max(400, perPatternWindow * patterns.length));
|
|
246
249
|
const combinedBulkHint = request.normalizedGlobPatterns.length === 0 && !request.fileType;
|
|
247
250
|
try {
|
|
248
|
-
return await request.runWindowedLines
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
{ offset: 0, limit: combinedCap, summaryLimit: 0, bulkHint: combinedBulkHint }
|
|
252
|
-
|
|
251
|
+
return await runSharedNativeScan(request.runWindowedLines, {
|
|
252
|
+
cwd: scope.cwd,
|
|
253
|
+
argv: combinedArgs,
|
|
254
|
+
window: { offset: 0, limit: combinedCap, summaryLimit: 0, bulkHint: combinedBulkHint },
|
|
255
|
+
scopes: [request.grepResolvedPath],
|
|
256
|
+
signal: options.signal,
|
|
257
|
+
});
|
|
253
258
|
} catch {
|
|
254
259
|
options.signal?.throwIfAborted();
|
|
255
260
|
return null;
|
|
@@ -90,7 +90,7 @@ function rebaseAbsoluteGlob(glob, rootFwd) {
|
|
|
90
90
|
};
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
async function resolveGrepScope(args, workDir) {
|
|
93
|
+
export async function resolveGrepScope(args, workDir) {
|
|
94
94
|
let searchPath = args.path || '.';
|
|
95
95
|
const rawGlobs = uniqueStrings(stringList(args.glob).map(normalizeInputPath));
|
|
96
96
|
if (hasGlobMagic(searchPath)) {
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// Shares only identical in-flight native scans (no persistent result). The
|
|
2
|
+
// existing single-flight layer isolates subscriber cancellation and detaches
|
|
3
|
+
// a scan after a mutation without interrupting its current readers.
|
|
4
|
+
import { runResultCacheInFlight } from '../cache-layers.mjs';
|
|
5
|
+
|
|
6
|
+
export function runSharedNativeScan(runWindowedLines, { cwd, argv, window, scopes, signal }) {
|
|
7
|
+
return runResultCacheInFlight(
|
|
8
|
+
`grep-path-scan:${JSON.stringify([cwd, argv, window])}`,
|
|
9
|
+
({ signal: sharedSignal }) => runWindowedLines(argv, { cwd, signal: sharedSignal }, window),
|
|
10
|
+
{ signal, scopes }
|
|
11
|
+
);
|
|
12
|
+
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { open } from 'node:fs/promises';
|
|
2
2
|
import { hashText } from './hash-utils.mjs';
|
|
3
|
+
import { runResultCacheInFlight } from './cache-layers.mjs';
|
|
3
4
|
import {
|
|
5
|
+
canonicalCachePath,
|
|
4
6
|
getReadRangeIndex,
|
|
5
7
|
nearestReadRangeAnchor,
|
|
6
8
|
maybeRecordReadRangeAnchor,
|
|
@@ -13,6 +15,18 @@ import { READ_MAX_SCAN_BYTES, READ_STREAM_TIMEOUT_MS } from './read-constants.mj
|
|
|
13
15
|
export async function readSourceWindows(path, intervals, { signal } = {}) {
|
|
14
16
|
signal?.throwIfAborted();
|
|
15
17
|
if (!intervals.length) return new Map();
|
|
18
|
+
const snapshot = intervals.map(({ start, end }) => ({ start, end }));
|
|
19
|
+
const key = `source-windows:${canonicalCachePath(path)}:${JSON.stringify(snapshot.map((w) => [w.start, w.end]))}`;
|
|
20
|
+
const shared = await runResultCacheInFlight(
|
|
21
|
+
key,
|
|
22
|
+
({ signal: sharedSignal }) => scanSourceWindows(path, snapshot, sharedSignal),
|
|
23
|
+
{ signal, scopes: [path] }
|
|
24
|
+
);
|
|
25
|
+
return new Map(shared);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function scanSourceWindows(path, intervals, signal) {
|
|
29
|
+
signal?.throwIfAborted();
|
|
16
30
|
const handle = await open(path, 'r');
|
|
17
31
|
try {
|
|
18
32
|
const st = await handle.stat();
|
|
@@ -1,30 +1,30 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.1.
|
|
2
|
+
"version": "0.1.31",
|
|
3
3
|
"_comment": "Synced from immutable graph-v release assets.",
|
|
4
4
|
"assets": {
|
|
5
5
|
"darwin-arm64": {
|
|
6
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
7
|
-
"sha256": "
|
|
6
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-darwin-arm64",
|
|
7
|
+
"sha256": "7fbbb99fd98da1fee7d3cdb90c73c9b4ec663a8ea108bbc0f7ee30e194571ed1"
|
|
8
8
|
},
|
|
9
9
|
"darwin-x64": {
|
|
10
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
11
|
-
"sha256": "
|
|
10
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-darwin-x64",
|
|
11
|
+
"sha256": "8cdbcdf3e207ead5ba274b87363ba1e8acc2c68d5912d95a29cd89cbecdc3c46"
|
|
12
12
|
},
|
|
13
13
|
"linux-arm64": {
|
|
14
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
15
|
-
"sha256": "
|
|
14
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-linux-arm64",
|
|
15
|
+
"sha256": "704361e56ee605d6dd5edcb014e3cc0c3e11b7909639d5d8cacc9d75ad1134d2"
|
|
16
16
|
},
|
|
17
17
|
"linux-x64": {
|
|
18
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
19
|
-
"sha256": "
|
|
18
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-linux-x64",
|
|
19
|
+
"sha256": "ef80b2bb372e98e5e71f11cf720fc709ec79f187d95c00bcef3b3c8d23f22f47"
|
|
20
20
|
},
|
|
21
21
|
"win32-arm64": {
|
|
22
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
23
|
-
"sha256": "
|
|
22
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-win32-arm64.exe",
|
|
23
|
+
"sha256": "8fe669f56b6bd17705cdd24e16e705a40dc4ffe210fad0e9692d51c365a9a072"
|
|
24
24
|
},
|
|
25
25
|
"win32-x64": {
|
|
26
|
-
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.
|
|
27
|
-
"sha256": "
|
|
26
|
+
"url": "https://github.com/tribgames/mixdog/releases/download/graph-v0.1.31/mixdog-graph-win32-x64.exe",
|
|
27
|
+
"sha256": "ca2e4045fd4757b4b4e39de9d4df7790d9bcccc083ffca96a6f0cffbb1130f1d"
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// target), then apply it in one shot under path locks with all-or-nothing
|
|
3
3
|
// rollback.
|
|
4
4
|
import { parsePatch } from 'diff';
|
|
5
|
+
import { throwIfAborted } from '../../../../../shared/abort-race.mjs';
|
|
5
6
|
import { withBuiltinPathLocks } from '../../builtin.mjs';
|
|
6
7
|
import { withAdvisoryLocks } from '../../builtin/advisory-lock.mjs';
|
|
7
8
|
import { wrapPatchMutationOutput } from '../mutation-output.mjs';
|
|
@@ -155,7 +156,7 @@ export async function runCodexBatch({ batch, basePath, v4aConvertOpts, rejectedV
|
|
|
155
156
|
// it whenever the batch fails — by returned Error text OR by a thrown error
|
|
156
157
|
// (V4A rename, persistence) — so mode:"atomic" really is all-or-nothing
|
|
157
158
|
// instead of leaving an earlier commit in place.
|
|
158
|
-
export function applyCodexBatchWithRollback({ batch, basePath, dryRun, readStateScope, options, runBatch }) {
|
|
159
|
+
export function applyCodexBatchWithRollback({ batch, basePath, dryRun, readStateScope, abortSignal, options, runBatch }) {
|
|
159
160
|
const { lockPaths, v4aRenamePlan } = batch;
|
|
160
161
|
const registerUiDiff = (rollbackSnapshots) =>
|
|
161
162
|
registerCommittedPatchUiDiff({
|
|
@@ -168,6 +169,7 @@ export function applyCodexBatchWithRollback({ batch, basePath, dryRun, readState
|
|
|
168
169
|
});
|
|
169
170
|
return withBuiltinPathLocks(lockPaths, () =>
|
|
170
171
|
withAdvisoryLocks(lockPaths, async () => {
|
|
172
|
+
throwIfAborted(abortSignal);
|
|
171
173
|
let rollbackSnapshots = [];
|
|
172
174
|
if (!dryRun) {
|
|
173
175
|
try {
|
|
@@ -8,6 +8,7 @@ import { countLabel, formatNativeSummary, kindLabel, traceNativeApply } from './
|
|
|
8
8
|
import { unlink } from 'node:fs/promises';
|
|
9
9
|
import { dirname as pathDirname } from 'node:path';
|
|
10
10
|
import { performance } from 'node:perf_hooks';
|
|
11
|
+
import { throwIfAborted } from '../../../../shared/abort-race.mjs';
|
|
11
12
|
import {
|
|
12
13
|
normalizeOutputPath,
|
|
13
14
|
invalidateBuiltinResultCache,
|
|
@@ -154,6 +155,7 @@ export async function dispatchNativePatch({
|
|
|
154
155
|
dryRun,
|
|
155
156
|
fuzzy: fuzz > 0,
|
|
156
157
|
readStateScope,
|
|
158
|
+
signal,
|
|
157
159
|
});
|
|
158
160
|
if (!/^Error:/i.test(String(recovered || '').trimStart())) {
|
|
159
161
|
return `${recovered}\n[native transport unavailable; create-only patch completed with the JS engine]`;
|
|
@@ -326,19 +328,19 @@ function findParsedForRow(row, parsed, basePath) {
|
|
|
326
328
|
return null;
|
|
327
329
|
}
|
|
328
330
|
|
|
329
|
-
async function applyJsParsedEntry(entry, basePath, { dryRun, fuzzy, readStateScope }) {
|
|
331
|
+
async function applyJsParsedEntry(entry, basePath, { dryRun, fuzzy, readStateScope, signal }) {
|
|
330
332
|
const kind = classifyEntry(entry);
|
|
331
333
|
const headerName = entryHeaderName(entry, kind);
|
|
332
334
|
const fullPath = resolveEntryPath(basePath, headerName);
|
|
333
335
|
const displayPath = normalizeOutputPath(stripDiffPrefix(headerName));
|
|
334
|
-
if (kind === 'create') await applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readStateScope });
|
|
335
|
-
else if (kind === 'delete') await applyJsDeleteEntry(fullPath, displayPath, { dryRun, readStateScope });
|
|
336
|
-
else await applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy, readStateScope });
|
|
336
|
+
if (kind === 'create') await applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readStateScope, signal });
|
|
337
|
+
else if (kind === 'delete') await applyJsDeleteEntry(fullPath, displayPath, { dryRun, readStateScope, signal });
|
|
338
|
+
else await applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy, readStateScope, signal });
|
|
337
339
|
const { added, removed } = countHunkChanges(entry.hunks);
|
|
338
340
|
return { kind, fullPath, displayPath, added, removed };
|
|
339
341
|
}
|
|
340
342
|
|
|
341
|
-
async function applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readStateScope }) {
|
|
343
|
+
async function applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readStateScope, signal }) {
|
|
342
344
|
// Add File is create-only. atomicWrite checks the absent-target
|
|
343
345
|
// expectation again so a file appearing after preflight is not replaced.
|
|
344
346
|
assertAddTargetAbsent(fullPath, displayPath);
|
|
@@ -350,9 +352,11 @@ async function applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readSt
|
|
|
350
352
|
}
|
|
351
353
|
const content = joinTextLinesForPatch(addedLines);
|
|
352
354
|
if (dryRun) return;
|
|
355
|
+
throwIfAborted(signal);
|
|
353
356
|
mkdirSync(pathDirname(fullPath), { recursive: true });
|
|
354
357
|
try {
|
|
355
358
|
await atomicWrite(fullPath, content, {
|
|
359
|
+
signal,
|
|
356
360
|
sessionId: readStateScope,
|
|
357
361
|
expectedTargetSnapshot: { exists: false },
|
|
358
362
|
});
|
|
@@ -369,16 +373,17 @@ async function applyJsCreateEntry(entry, fullPath, displayPath, { dryRun, readSt
|
|
|
369
373
|
recordReadSnapshotForPath(fullPath, readStateScope, { source: 'apply_patch_js', isPartialView: false });
|
|
370
374
|
}
|
|
371
375
|
|
|
372
|
-
async function applyJsDeleteEntry(fullPath, displayPath, { dryRun, readStateScope }) {
|
|
376
|
+
async function applyJsDeleteEntry(fullPath, displayPath, { dryRun, readStateScope, signal }) {
|
|
373
377
|
lstatRegularPatchFile(fullPath, displayPath);
|
|
374
378
|
if (dryRun) return;
|
|
379
|
+
throwIfAborted(signal);
|
|
375
380
|
await unlink(fullPath);
|
|
376
381
|
invalidateBuiltinResultCache([fullPath]);
|
|
377
382
|
markCodeGraphDirtyPaths([fullPath]);
|
|
378
383
|
clearReadSnapshotForPath(fullPath, readStateScope);
|
|
379
384
|
}
|
|
380
385
|
|
|
381
|
-
async function applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy, readStateScope }) {
|
|
386
|
+
async function applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy, readStateScope, signal }) {
|
|
382
387
|
const preMutationStat = lstatRegularPatchFile(fullPath, displayPath);
|
|
383
388
|
const { writePath, snapshotStat } = resolvePatchWriteTarget(fullPath, displayPath, preMutationStat);
|
|
384
389
|
// Rewrite path: decode by BOM, re-encode into the same codec, and keep every
|
|
@@ -393,11 +398,13 @@ async function applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy,
|
|
|
393
398
|
});
|
|
394
399
|
const content = encodePatchTargetContent(joinTextLinesForPatch(updatedLines), rawEnc);
|
|
395
400
|
if (dryRun) return;
|
|
401
|
+
throwIfAborted(signal);
|
|
396
402
|
// The expected-target snapshot closes the read→write window: if anything
|
|
397
403
|
// rewrote this file after we read it, the write fails instead of silently
|
|
398
404
|
// clobbering it (and instead of inheriting a stale "body delivered" claim).
|
|
399
405
|
try {
|
|
400
406
|
await atomicWrite(writePath, content, {
|
|
407
|
+
signal,
|
|
401
408
|
sessionId: readStateScope,
|
|
402
409
|
expectedTargetSnapshot: snapshotStat
|
|
403
410
|
? {
|
|
@@ -427,13 +434,14 @@ async function applyJsUpdateEntry(entry, fullPath, displayPath, { dryRun, fuzzy,
|
|
|
427
434
|
});
|
|
428
435
|
}
|
|
429
436
|
|
|
430
|
-
export async function dispatchJsPatchEntries({ rows, parsed, basePath, dryRun, fuzzy, readStateScope }) {
|
|
437
|
+
export async function dispatchJsPatchEntries({ rows, parsed, basePath, dryRun, fuzzy, readStateScope, signal }) {
|
|
431
438
|
const applied = [];
|
|
432
439
|
for (const row of rows || []) {
|
|
433
440
|
const entry = findParsedForRow(row, parsed, basePath);
|
|
434
441
|
if (!entry) throw new Error(`apply_patch: missing parsed entry for ${row.displayPath}`);
|
|
435
442
|
try {
|
|
436
|
-
|
|
443
|
+
throwIfAborted(signal);
|
|
444
|
+
applied.push(await applyJsParsedEntry(entry, basePath, { dryRun, fuzzy, readStateScope, signal }));
|
|
437
445
|
} catch (err) {
|
|
438
446
|
return `Error: ${err?.message || String(err)}`;
|
|
439
447
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// JS writer split. Both the Codex batch and the ordered sequence share these
|
|
3
3
|
// apply semantics.
|
|
4
4
|
import { parsePatch } from 'diff';
|
|
5
|
+
import { throwIfAborted } from '../../../../shared/abort-race.mjs';
|
|
5
6
|
import { symlinkWriteTarget } from '../builtin/atomic-write.mjs';
|
|
6
7
|
import { dispatchNativePatch, dispatchJsPatchEntries } from './dispatch.mjs';
|
|
7
8
|
import { patchTargetUsesUtf16 } from './matcher.mjs';
|
|
@@ -33,6 +34,7 @@ export function parseConvertedUnifiedPatch(unified) {
|
|
|
33
34
|
// whether earlier waves already committed to disk.
|
|
34
35
|
export async function applyParsedWave({ parsed: wparsed, entries: wentries, headerRewrites: whr }, basePath, opts) {
|
|
35
36
|
const { fuzz, rejectPartial, dryRun, fuzzy, readStateScope, abortSignal } = opts;
|
|
37
|
+
throwIfAborted(abortSignal);
|
|
36
38
|
// Create entries use the JS atomic writer even inside the base path. Its
|
|
37
39
|
// expected-absent snapshot makes Add File create-only under external races;
|
|
38
40
|
// the native patch engine intentionally supports overwrite-style additions.
|
|
@@ -47,6 +49,7 @@ export async function applyParsedWave({ parsed: wparsed, entries: wentries, head
|
|
|
47
49
|
// binary that predates this build's byte-fidelity contract is not used at
|
|
48
50
|
// all — everything takes the JS writer instead.
|
|
49
51
|
const engineContractOk = await nativePatchSessionSatisfiesContract();
|
|
52
|
+
throwIfAborted(abortSignal);
|
|
50
53
|
// Codec routing only concerns entries that REWRITE bytes; a delete does not.
|
|
51
54
|
// An unverified engine, however, must receive no work at all — deletes
|
|
52
55
|
// included — so the whole wave takes the JS writer.
|
|
@@ -117,6 +120,7 @@ export async function applyParsedWave({ parsed: wparsed, entries: wentries, head
|
|
|
117
120
|
dryRun,
|
|
118
121
|
fuzzy,
|
|
119
122
|
readStateScope,
|
|
123
|
+
signal: abortSignal,
|
|
120
124
|
});
|
|
121
125
|
if (isPatchErrorText(jsResult)) return { executor, error: jsResult };
|
|
122
126
|
resultParts.push(jsResult);
|
|
@@ -404,7 +404,7 @@ export const BROWSER_INPUT_FIELDS = {
|
|
|
404
404
|
background: {
|
|
405
405
|
type: 'boolean',
|
|
406
406
|
description:
|
|
407
|
-
'true creates/targets a hidden support page; false reveals it; omitted never promotes a named one. open without true keeps it for user handoff.',
|
|
407
|
+
'true creates/targets a hidden support page; false reveals it until task end; omitted never promotes a named one. open without true keeps it for user handoff.',
|
|
408
408
|
},
|
|
409
409
|
},
|
|
410
410
|
};
|
|
@@ -22,7 +22,7 @@ import { BROWSER_INPUT_FIELDS } from './input-fields.mjs';
|
|
|
22
22
|
const BROWSER_DESCRIPTION =
|
|
23
23
|
"Drive Mixdog's in-app Chromium directly for rendered, signed-in or interactive pages; public page text uses web_fetch, external windows computer. " +
|
|
24
24
|
'Load the browser-use skill before first use. ' +
|
|
25
|
-
'Background
|
|
25
|
+
'Background by default; reveal only via open for a deliverable or handoff. Respect user-closed panels. ' +
|
|
26
26
|
'Task end cleans owned pages/restores temporary panels; user pages persist. ' +
|
|
27
27
|
'hide folds the panel; close_tab closes named background pages only. ' +
|
|
28
28
|
'Pages are session-local; sign-in/storage shared; never provide session_id. ' +
|
|
@@ -72,19 +72,6 @@ for (const name of [
|
|
|
72
72
|
'key',
|
|
73
73
|
].includes(name),
|
|
74
74
|
focusGuard: ['invoke', 'set_value', 'toggle'].includes(name),
|
|
75
|
-
focusContinuation: [
|
|
76
|
-
'invoke',
|
|
77
|
-
'click',
|
|
78
|
-
'double_click',
|
|
79
|
-
'right_click',
|
|
80
|
-
'middle_click',
|
|
81
|
-
'triple_click',
|
|
82
|
-
'mouse_down',
|
|
83
|
-
'mouse_up',
|
|
84
|
-
'mouse_move',
|
|
85
|
-
'drag',
|
|
86
|
-
'scroll',
|
|
87
|
-
].includes(name),
|
|
88
75
|
policy: 'act',
|
|
89
76
|
});
|
|
90
77
|
}
|
|
@@ -11,6 +11,7 @@ import { randomUUID } from 'node:crypto';
|
|
|
11
11
|
import { resolveAntigravityAuth } from '../auth.mjs';
|
|
12
12
|
import { timeoutSignal } from '../bounded-signal.mjs';
|
|
13
13
|
import { upstreamError } from '../upstream-error.mjs';
|
|
14
|
+
import { geminiUsage } from '../media-usage.mjs';
|
|
14
15
|
import { geminiImageRequestBody, pickGeminiImagePart } from './gemini-image.mjs';
|
|
15
16
|
import { CONTENT_ENDPOINTS, antigravityHeaders } from '../../agent/orchestrator/providers/antigravity-oauth-tokens.mjs';
|
|
16
17
|
import { frameAndParseSse } from '../../agent/orchestrator/providers/lib/sse-framing.mjs';
|
|
@@ -35,6 +36,7 @@ function antigravityImageRequestBody({ projectId, model, prompt, options = {}, r
|
|
|
35
36
|
export function antigravityImageParts(sseText) {
|
|
36
37
|
const parts = [];
|
|
37
38
|
let failure = null;
|
|
39
|
+
let usageMetadata = null;
|
|
38
40
|
for (const event of frameAndParseSse(sseText).events) {
|
|
39
41
|
if (event.error) continue;
|
|
40
42
|
const value = event.value;
|
|
@@ -43,10 +45,12 @@ export function antigravityImageParts(sseText) {
|
|
|
43
45
|
const chunk = value?.response && typeof value.response === 'object' ? value.response : value;
|
|
44
46
|
const candidate = chunk?.candidates?.[0];
|
|
45
47
|
parts.push(...(candidate?.content?.parts || []));
|
|
48
|
+
// usageMetadata is cumulative; the last chunk that carries it is the total.
|
|
49
|
+
if (chunk?.usageMetadata) usageMetadata = chunk.usageMetadata;
|
|
46
50
|
const blocked = chunk?.promptFeedback?.blockReason;
|
|
47
51
|
if (blocked && !failure) failure = `prompt blocked (${blocked})`;
|
|
48
52
|
}
|
|
49
|
-
return { parts, failure };
|
|
53
|
+
return { parts, failure, usage: geminiUsage(usageMetadata) };
|
|
50
54
|
}
|
|
51
55
|
|
|
52
56
|
export async function generateImage(
|
|
@@ -68,6 +72,6 @@ export async function generateImage(
|
|
|
68
72
|
signal: timeoutSignal(signal, REQUEST_TIMEOUT_MS),
|
|
69
73
|
});
|
|
70
74
|
if (!res.ok) throw upstreamError('Antigravity image', res.status, await res.text().catch(() => ''));
|
|
71
|
-
const { parts, failure } = antigravityImageParts(await res.text());
|
|
72
|
-
return pickGeminiImagePart(parts, 'Antigravity', failure);
|
|
75
|
+
const { parts, failure, usage } = antigravityImageParts(await res.text());
|
|
76
|
+
return pickGeminiImagePart(parts, 'Antigravity', failure, usage);
|
|
73
77
|
}
|
|
@@ -12,6 +12,7 @@ import { timeoutSignal } from '../bounded-signal.mjs';
|
|
|
12
12
|
import { decodeBase64Media } from '../download.mjs';
|
|
13
13
|
import { mediaError } from '../lanes.mjs';
|
|
14
14
|
import { upstreamError } from '../upstream-error.mjs';
|
|
15
|
+
import { responsesUsage, withReportedUsage } from '../media-usage.mjs';
|
|
15
16
|
import { CODEX_OAUTH_ORIGINATOR, CODEX_RESPONSES_URL } from '../../agent/orchestrator/providers/openai-oauth.mjs';
|
|
16
17
|
import {
|
|
17
18
|
codexUserAgent,
|
|
@@ -32,6 +33,12 @@ function imageTool(options = {}) {
|
|
|
32
33
|
|
|
33
34
|
function collectImage(event, state) {
|
|
34
35
|
const item = event?.item;
|
|
36
|
+
// Terminal Responses events carry the orchestrator's token usage. It does
|
|
37
|
+
// not include the hosted image tool's own tokens.
|
|
38
|
+
if (['response.completed', 'response.failed', 'response.incomplete'].includes(event?.type)) {
|
|
39
|
+
state.usage = responsesUsage(event?.response?.usage) || state.usage;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
35
42
|
if (event?.type === 'response.output_item.done' && item?.type === 'image_generation_call') {
|
|
36
43
|
if (typeof item.result === 'string' && item.result.length > 0) state.final = item.result;
|
|
37
44
|
if (typeof item.model === 'string') state.actualModel = item.model;
|
|
@@ -142,12 +149,16 @@ export async function generateImage(
|
|
|
142
149
|
|
|
143
150
|
const b64 = state.final || state.partial;
|
|
144
151
|
if (!b64) {
|
|
145
|
-
throw
|
|
152
|
+
throw withReportedUsage(
|
|
153
|
+
mediaError(`ChatGPT returned no image data${failure ? `: ${failure}` : ''}`, 'MEDIA_EMPTY_RESULT', 502),
|
|
154
|
+
state.usage
|
|
155
|
+
);
|
|
146
156
|
}
|
|
147
157
|
return {
|
|
148
158
|
bytes: decodeBase64Media(b64, 'ChatGPT image'),
|
|
149
159
|
mime: 'image/png',
|
|
150
160
|
revisedPrompt: state.revisedPrompt,
|
|
151
161
|
actualModel: state.actualModel || null,
|
|
162
|
+
usage: state.usage || null,
|
|
152
163
|
};
|
|
153
164
|
}
|
|
@@ -11,6 +11,7 @@ import { timeoutSignal } from '../bounded-signal.mjs';
|
|
|
11
11
|
import { decodeBase64Media } from '../download.mjs';
|
|
12
12
|
import { mediaError } from '../lanes.mjs';
|
|
13
13
|
import { upstreamError } from '../upstream-error.mjs';
|
|
14
|
+
import { geminiUsage, withReportedUsage } from '../media-usage.mjs';
|
|
14
15
|
|
|
15
16
|
const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta/models';
|
|
16
17
|
const REQUEST_TIMEOUT_MS = 180_000;
|
|
@@ -51,20 +52,25 @@ async function post(model, key, body, signal, fetchFn) {
|
|
|
51
52
|
* First inline image among a candidate's parts. A text-only answer is the
|
|
52
53
|
* model's refusal and surfaces as such; it is never retried.
|
|
53
54
|
*/
|
|
54
|
-
export function pickGeminiImagePart(parts, label, failure = null) {
|
|
55
|
+
export function pickGeminiImagePart(parts, label, failure = null, usage = null) {
|
|
55
56
|
const image = parts.find((part) => part?.inlineData?.data);
|
|
56
57
|
if (!image) {
|
|
57
58
|
const refusal = failure || parts.find((part) => typeof part?.text === 'string' && part.text.trim())?.text || '';
|
|
58
|
-
throw
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
59
|
+
throw withReportedUsage(
|
|
60
|
+
mediaError(
|
|
61
|
+
`${label} returned no image data${refusal ? `: ${String(refusal).slice(0, 200)}` : ''}`,
|
|
62
|
+
'MEDIA_EMPTY_RESULT',
|
|
63
|
+
502
|
|
64
|
+
),
|
|
65
|
+
usage
|
|
62
66
|
);
|
|
63
67
|
}
|
|
64
68
|
return {
|
|
65
69
|
bytes: decodeBase64Media(image.inlineData.data, `${label} image`),
|
|
66
70
|
mime: image.inlineData.mimeType || 'image/png',
|
|
67
71
|
revisedPrompt: null,
|
|
72
|
+
// Without reported tokens the one image is priced per image, if the catalog lists it.
|
|
73
|
+
usage: usage || { images: 1 },
|
|
68
74
|
};
|
|
69
75
|
}
|
|
70
76
|
|
|
@@ -76,5 +82,10 @@ export async function generateImage(
|
|
|
76
82
|
const res = await post(model, key, geminiImageRequestBody(prompt, options, references), signal, fetchFn);
|
|
77
83
|
if (!res.ok) throw upstreamError('Gemini image', res.status, await res.text().catch(() => ''));
|
|
78
84
|
const data = await res.json();
|
|
79
|
-
return pickGeminiImagePart(
|
|
85
|
+
return pickGeminiImagePart(
|
|
86
|
+
data?.candidates?.[0]?.content?.parts || [],
|
|
87
|
+
'Gemini',
|
|
88
|
+
null,
|
|
89
|
+
geminiUsage(data?.usageMetadata)
|
|
90
|
+
);
|
|
80
91
|
}
|
|
@@ -12,7 +12,9 @@ import { boundedSignal } from '../bounded-signal.mjs';
|
|
|
12
12
|
import { decodeBase64Media, downloadGeminiMedia } from '../download.mjs';
|
|
13
13
|
import { mediaError } from '../lanes.mjs';
|
|
14
14
|
import { upstreamError } from '../upstream-error.mjs';
|
|
15
|
+
import { interactionsUsage, withReportedUsage } from '../media-usage.mjs';
|
|
15
16
|
|
|
17
|
+
const VEO_DEFAULT_SECONDS = 8;
|
|
16
18
|
const BASE_URL = 'https://generativelanguage.googleapis.com/v1beta';
|
|
17
19
|
const OMNI_TIMEOUT_MS = 600_000;
|
|
18
20
|
const POLL_INTERVAL_MS = 8_000;
|
|
@@ -54,14 +56,15 @@ async function generateViaOmni({ model, prompt, options, references = [], signal
|
|
|
54
56
|
});
|
|
55
57
|
if (!res.ok) throw upstreamError('Gemini Omni video', res.status, await res.text().catch(() => ''));
|
|
56
58
|
const data = await res.json();
|
|
59
|
+
const usage = interactionsUsage(data?.usage);
|
|
57
60
|
for (const step of data?.steps || []) {
|
|
58
61
|
const content = Array.isArray(step?.content) ? step.content : [step?.content].filter(Boolean);
|
|
59
62
|
const video = content.find((item) => item?.type === 'video' && typeof item?.data === 'string');
|
|
60
63
|
if (video) {
|
|
61
|
-
return { bytes: decodeBase64Media(video.data, 'Gemini Omni video'), mime: video.mime_type || 'video/mp4' };
|
|
64
|
+
return { bytes: decodeBase64Media(video.data, 'Gemini Omni video'), mime: video.mime_type || 'video/mp4', usage };
|
|
62
65
|
}
|
|
63
66
|
}
|
|
64
|
-
throw mediaError('Gemini Omni returned no video data', 'MEDIA_EMPTY_RESULT', 502);
|
|
67
|
+
throw withReportedUsage(mediaError('Gemini Omni returned no video data', 'MEDIA_EMPTY_RESULT', 502), usage);
|
|
65
68
|
}
|
|
66
69
|
|
|
67
70
|
async function generateViaVeo({ model, prompt, options, references = [], signal, onProgress, key }) {
|
|
@@ -114,13 +117,20 @@ async function generateViaVeo({ model, prompt, options, references = [], signal,
|
|
|
114
117
|
const sample = data?.response?.generateVideoResponse?.generatedSamples?.[0] || data?.response?.generatedVideos?.[0];
|
|
115
118
|
const uri = sample?.video?.uri || sample?.video?.fileUri;
|
|
116
119
|
if (!uri) throw mediaError('Veo finished without a video URI', 'MEDIA_EMPTY_RESULT', 502);
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
120
|
+
// Veo reports no usage; it bills per generated second. The response does
|
|
121
|
+
// not echo the length, so the requested one (default 8s) is the billed one.
|
|
122
|
+
// A failed download still leaves the generated seconds billed.
|
|
123
|
+
const usage = { seconds: parameters.durationSeconds || VEO_DEFAULT_SECONDS, resolution: parameters.resolution || '720p' };
|
|
124
|
+
// The download is part of the generation budget: unbounded, it held an
|
|
125
|
+
// active job slot (and the upstream connection) open indefinitely after
|
|
126
|
+
// the poll loop finished.
|
|
127
|
+
const bytes = await downloadGeminiMedia(uri, {
|
|
128
|
+
key,
|
|
129
|
+
signal: boundedSignal(signal, deadline, TOTAL_TIMEOUT_MS),
|
|
130
|
+
}).catch((error) => {
|
|
131
|
+
throw withReportedUsage(error, usage);
|
|
132
|
+
});
|
|
133
|
+
return { bytes, mime: 'video/mp4', usage };
|
|
124
134
|
}
|
|
125
135
|
}
|
|
126
136
|
|
|
@@ -10,6 +10,7 @@ import { boundedSignal, timeoutSignal } from '../bounded-signal.mjs';
|
|
|
10
10
|
import { decodeBase64Media, downloadPublicMedia } from '../download.mjs';
|
|
11
11
|
import { mediaError } from '../lanes.mjs';
|
|
12
12
|
import { upstreamError } from '../upstream-error.mjs';
|
|
13
|
+
import { withReportedUsage, xaiUsage } from '../media-usage.mjs';
|
|
13
14
|
|
|
14
15
|
const POLL_INTERVAL_MS = 4_000;
|
|
15
16
|
const START_TIMEOUT_MS = 60_000;
|
|
@@ -72,6 +73,7 @@ export async function generateImage({ lane, model, prompt, options = {}, referen
|
|
|
72
73
|
bytes: decodeBase64Media(entry.b64_json, 'xAI image'),
|
|
73
74
|
mime: entry.mime_type || 'image/png',
|
|
74
75
|
revisedPrompt: entry.revised_prompt || null,
|
|
76
|
+
usage: xaiUsage(data?.usage),
|
|
75
77
|
};
|
|
76
78
|
}
|
|
77
79
|
|
|
@@ -114,18 +116,29 @@ export async function generateVideo({ lane, model, prompt, options = {}, referen
|
|
|
114
116
|
if (typeof data?.progress === 'number' && typeof onProgress === 'function') onProgress(data.progress);
|
|
115
117
|
if (data?.status === 'done') {
|
|
116
118
|
const url = data?.video?.url;
|
|
117
|
-
|
|
119
|
+
// The billed cost (when reported) is the price; seconds + resolution
|
|
120
|
+
// let the catalog's per-second rate price it otherwise.
|
|
121
|
+
const seconds = Number(data?.video?.duration) || duration;
|
|
122
|
+
const usage = { ...xaiUsage(data?.usage), seconds, resolution };
|
|
123
|
+
if (!url) throw withReportedUsage(mediaError('xAI video finished without a URL', 'MEDIA_EMPTY_RESULT', 502), usage);
|
|
124
|
+
const bytes = await downloadPublicMedia(url, { signal, label: 'xAI video' }).catch((error) => {
|
|
125
|
+
throw withReportedUsage(error, usage);
|
|
126
|
+
});
|
|
118
127
|
return {
|
|
119
|
-
bytes
|
|
128
|
+
bytes,
|
|
120
129
|
mime: 'video/mp4',
|
|
121
|
-
durationSeconds:
|
|
130
|
+
durationSeconds: seconds,
|
|
131
|
+
usage,
|
|
122
132
|
};
|
|
123
133
|
}
|
|
124
134
|
if (data?.status === 'failed' || data?.status === 'expired') {
|
|
125
|
-
throw
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
135
|
+
throw withReportedUsage(
|
|
136
|
+
mediaError(
|
|
137
|
+
`xAI video ${data.status}${data?.error?.code ? `: ${data.error.code}` : ''}`,
|
|
138
|
+
'MEDIA_UPSTREAM_FAILED',
|
|
139
|
+
502
|
|
140
|
+
),
|
|
141
|
+
xaiUsage(data?.usage)
|
|
129
142
|
);
|
|
130
143
|
}
|
|
131
144
|
}
|