dsh-agy-link 0.3.1 โ 0.3.3
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 +1 -1
- package/dist/index.js +286 -242
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ DSH-side tools and permissions are unaffected โ this only governs what the spa
|
|
|
223
223
|
|
|
224
224
|
## ๐งฉ How it works
|
|
225
225
|
|
|
226
|
-
One DSH turn = one short-lived `agy -p --output-format stream-json` process. The NDJSON event stream is parsed, normalized, and recorded. Spans of that recording are mapped to DSH StreamChunks โ thinking โ reasoning blocks, text โ text blocks, result envelope โ usage + finish โ and each **completed agy tool step cuts the span** with a `tool-calls` finish
|
|
226
|
+
One DSH turn = one short-lived `agy -p --output-format stream-json` process. The NDJSON event stream is parsed, normalized, and recorded. Spans of that recording are mapped to DSH StreamChunks โ thinking โ reasoning blocks, text โ text blocks, result envelope โ usage + finish โ and each **completed agy tool step cuts the span** with a `tool-calls` finish. The tool-call block addresses `run_code` โ the only tool DSH's dispatch policy lets a model call directly โ wrapping a generated one-line program that invokes the internal `agy_tool` mirror; the inner dispatch instantly replays the recorded output, writes real `tool/call` + `tool/result` session events, and re-calls the provider to continue the run. Tool activity therefore renders with DSH's **native tool-card UI** (terminal cards, diffs, read/search icons) instead of text annotations, and cards read agy's PascalCase arg keys (`CommandLine`, `AbsolutePath`, โฆ). Conversation ids come from the stream itself, with a conversations-directory snapshot diff as fallback. **No reverse-engineered database scraping, no protobuf decoding, no token-file access** โ only the official unmodified agy binary is spawned.
|
|
227
227
|
|
|
228
228
|
## ๐ What it cannot do (honest list)
|
|
229
229
|
|
package/dist/index.js
CHANGED
|
@@ -3,9 +3,9 @@ import { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync
|
|
|
3
3
|
import { homedir, tmpdir } from "node:os";
|
|
4
4
|
import { CallId, LlmAdapter, LlmError } from "@deepseek-ai/dsh-llm";
|
|
5
5
|
import { randomBytes, randomUUID } from "node:crypto";
|
|
6
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
6
7
|
import { mkdir, mkdtemp, readFile, readdir, rm, stat, unlink, writeFile } from "node:fs/promises";
|
|
7
8
|
import { spawn } from "node:child_process";
|
|
8
|
-
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
9
|
import { createServer } from "node:http";
|
|
10
10
|
import { fileURLToPath } from "node:url";
|
|
11
11
|
//#region src/common/types.ts
|
|
@@ -178,7 +178,7 @@ function readJson(file) {
|
|
|
178
178
|
function readOverrides(file = overridesPath()) {
|
|
179
179
|
return readJson(file);
|
|
180
180
|
}
|
|
181
|
-
function asString
|
|
181
|
+
function asString(v) {
|
|
182
182
|
return typeof v === "string" ? v : void 0;
|
|
183
183
|
}
|
|
184
184
|
function asBool(v) {
|
|
@@ -207,11 +207,11 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
207
207
|
const cfg = {
|
|
208
208
|
...base,
|
|
209
209
|
enabled: asBool(get("enabled")) ?? base.enabled,
|
|
210
|
-
agyBin: asString
|
|
210
|
+
agyBin: asString(get("agyBin")) ?? base.agyBin,
|
|
211
211
|
extraArgs: Array.isArray(get("extraArgs")) ? get("extraArgs").filter((x) => typeof x === "string") : base.extraArgs,
|
|
212
212
|
permissionMode: asMode(get("permissionMode")) ?? base.permissionMode,
|
|
213
|
-
defaultModel: asString
|
|
214
|
-
defaultEffort: asString
|
|
213
|
+
defaultModel: asString(get("defaultModel")) ?? base.defaultModel,
|
|
214
|
+
defaultEffort: asString(get("defaultEffort")) ?? base.defaultEffort,
|
|
215
215
|
timeoutMs: asNum(get("timeoutMs")) ?? base.timeoutMs,
|
|
216
216
|
maxConcurrent: asNum(get("maxConcurrent")) ?? base.maxConcurrent,
|
|
217
217
|
contextWindowDefault: asNum(get("contextWindowDefault")) ?? base.contextWindowDefault,
|
|
@@ -221,15 +221,15 @@ function resolveConfig(entry, env = process.env, overrides = readOverrides()) {
|
|
|
221
221
|
modelsCacheTtlMs: asNum(get("modelsCacheTtlMs")) ?? base.modelsCacheTtlMs,
|
|
222
222
|
allowAuxiliary: asBool(get("allowAuxiliary")) ?? base.allowAuxiliary,
|
|
223
223
|
compactionMaxChars: asNum(get("compactionMaxChars")) ?? base.compactionMaxChars,
|
|
224
|
-
workspaceRoot: asString
|
|
224
|
+
workspaceRoot: asString(get("workspaceRoot")) ?? base.workspaceRoot,
|
|
225
225
|
fallbackModels: Array.isArray(get("fallbackModels")) ? get("fallbackModels").filter((x) => !!x && typeof x === "object" && typeof x.id === "string") : base.fallbackModels,
|
|
226
226
|
askTool: asBool(get("askTool")) ?? base.askTool,
|
|
227
|
-
mediaDir: asString
|
|
227
|
+
mediaDir: asString(get("mediaDir")) ?? base.mediaDir,
|
|
228
228
|
mediaTtlMs: asNum(get("mediaTtlMs")) ?? base.mediaTtlMs,
|
|
229
229
|
mediaMaxBytes: asNum(get("mediaMaxBytes")) ?? base.mediaMaxBytes,
|
|
230
230
|
mediaMaxImages: asNum(get("mediaMaxImages")) ?? base.mediaMaxImages,
|
|
231
231
|
mcpBridge: asBool(get("mcpBridge")) ?? base.mcpBridge,
|
|
232
|
-
mcpToolAllowlist: asString
|
|
232
|
+
mcpToolAllowlist: asString(get("mcpToolAllowlist")) ?? base.mcpToolAllowlist
|
|
233
233
|
};
|
|
234
234
|
if (env.DSH_AGY_ENABLED !== void 0) cfg.enabled = asBool(env.DSH_AGY_ENABLED) ?? cfg.enabled;
|
|
235
235
|
if (env.DSH_AGY_BIN) cfg.agyBin = env.DSH_AGY_BIN;
|
|
@@ -451,6 +451,261 @@ var RunRegistry = class {
|
|
|
451
451
|
}
|
|
452
452
|
};
|
|
453
453
|
//#endregion
|
|
454
|
+
//#region src/host/mirror-tool.ts
|
|
455
|
+
const MIRROR_TOOL_NAME = "agy_tool";
|
|
456
|
+
/** The only tool this DSH deployment lets a model call directly. */
|
|
457
|
+
const WRAPPER_TOOL_NAME = "run_code";
|
|
458
|
+
/**
|
|
459
|
+
* Build the run_code tool-call arguments that replay one recorded agy tool
|
|
460
|
+
* step. The deployment's tool-dispatch policy only allows `run_code` as a
|
|
461
|
+
* direct model call (everything else must be invoked from inside a
|
|
462
|
+
* program), so the span cut addresses run_code with a generated program
|
|
463
|
+
* whose single statement calls the registered mirror tool โ the inner
|
|
464
|
+
* dispatch is what renders the native tool card.
|
|
465
|
+
*/
|
|
466
|
+
function buildMirrorRunCode(runId, eventIndex, toolName) {
|
|
467
|
+
const invocation = JSON.stringify({
|
|
468
|
+
run: runId,
|
|
469
|
+
step: eventIndex
|
|
470
|
+
});
|
|
471
|
+
return {
|
|
472
|
+
code: "// dsh-agy-link mirror: replay recorded agy tool step " + eventIndex + " (" + toolName + ")\nreturn await tools['agy_tool'](" + invocation + ")",
|
|
473
|
+
description: "replay agy tool step " + eventIndex + " ยท " + toolName
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
/** agy serializes some tool args as a JSON string; presenters get an object. */
|
|
477
|
+
function toolInput(args) {
|
|
478
|
+
const raw = args.input;
|
|
479
|
+
if (raw === void 0 || raw === null) return {};
|
|
480
|
+
if (typeof raw === "object") return raw;
|
|
481
|
+
if (typeof raw === "string") try {
|
|
482
|
+
const parsed = JSON.parse(raw);
|
|
483
|
+
return typeof parsed === "object" && parsed !== null ? parsed : { value: parsed };
|
|
484
|
+
} catch {
|
|
485
|
+
return { value: raw };
|
|
486
|
+
}
|
|
487
|
+
return { value: raw };
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* agy serializes tool args with PascalCase keys (CommandLine, AbsolutePath,
|
|
491
|
+
* SearchDirectory, โฆ); pick() accepts both spellings so cards always read
|
|
492
|
+
* the real field instead of falling back to raw JSON.
|
|
493
|
+
*/
|
|
494
|
+
function pick$1(input, ...keys) {
|
|
495
|
+
for (const k of keys) {
|
|
496
|
+
const v = input[k];
|
|
497
|
+
if (typeof v === "string") return v;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/** Native pending-card projection for one mirrored agy tool call. */
|
|
501
|
+
function presentMirrorCall(args) {
|
|
502
|
+
const a = args;
|
|
503
|
+
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
504
|
+
const input = toolInput(a);
|
|
505
|
+
switch (name) {
|
|
506
|
+
case "run_command":
|
|
507
|
+
case "bash":
|
|
508
|
+
case "execute_command": return {
|
|
509
|
+
card: "terminal",
|
|
510
|
+
title: pick$1(input, "command", "cmd", "CommandLine", "Cmd") ?? JSON.stringify(input),
|
|
511
|
+
...pick$1(input, "description", "Description") !== void 0 ? { description: pick$1(input, "description", "Description") } : {},
|
|
512
|
+
...pick$1(input, "cwd", "Cwd", "WorkingDirectory") !== void 0 ? { cwd: pick$1(input, "cwd", "Cwd", "WorkingDirectory") } : {}
|
|
513
|
+
};
|
|
514
|
+
case "write_to_file":
|
|
515
|
+
case "write_file":
|
|
516
|
+
case "create_file": {
|
|
517
|
+
const path = pick$1(input, "path", "file_path", "filename", "Path", "FilePath", "FileName", "AbsolutePath") ?? "file";
|
|
518
|
+
const content = pick$1(input, "content", "Content", "FileContents", "contents") ?? "";
|
|
519
|
+
return {
|
|
520
|
+
card: "diff",
|
|
521
|
+
title: "Write " + path,
|
|
522
|
+
diffs: [{
|
|
523
|
+
path,
|
|
524
|
+
oldText: null,
|
|
525
|
+
newText: content
|
|
526
|
+
}],
|
|
527
|
+
locations: [{ path }]
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
case "edit_file":
|
|
531
|
+
case "replace_in_file":
|
|
532
|
+
case "edit": {
|
|
533
|
+
const path = pick$1(input, "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? "file";
|
|
534
|
+
const newText = pick$1(input, "new_string", "newText", "content", "NewString", "NewText", "Content") ?? "";
|
|
535
|
+
const oldText = pick$1(input, "old_string", "oldText", "OldString", "OldText") ?? null;
|
|
536
|
+
return {
|
|
537
|
+
card: "diff",
|
|
538
|
+
title: "Edit " + path,
|
|
539
|
+
diffs: [{
|
|
540
|
+
path,
|
|
541
|
+
oldText,
|
|
542
|
+
newText
|
|
543
|
+
}],
|
|
544
|
+
locations: [{ path }]
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
case "read_file":
|
|
548
|
+
case "view_file":
|
|
549
|
+
case "read":
|
|
550
|
+
case "open_file": {
|
|
551
|
+
const path = pick$1(input, "path", "file_path", "filename", "Path", "FilePath", "FileName", "AbsolutePath") ?? "";
|
|
552
|
+
const offset = typeof input.offset === "number" ? input.offset : typeof input.Offset === "number" ? input.Offset : void 0;
|
|
553
|
+
return {
|
|
554
|
+
card: "generic",
|
|
555
|
+
title: "Read " + path,
|
|
556
|
+
kind: "read",
|
|
557
|
+
...path !== "" ? { locations: [{
|
|
558
|
+
path,
|
|
559
|
+
...offset !== void 0 ? { line: offset + 1 } : {}
|
|
560
|
+
}] } : {}
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
case "find_by_name":
|
|
564
|
+
case "glob":
|
|
565
|
+
case "search_files":
|
|
566
|
+
case "search":
|
|
567
|
+
case "search_file_content":
|
|
568
|
+
case "grep": {
|
|
569
|
+
const q = pick$1(input, "pattern", "query", "regex", "Pattern", "Query", "Regex", "QueryString") ?? "";
|
|
570
|
+
return {
|
|
571
|
+
card: "generic",
|
|
572
|
+
title: q !== "" ? "Search " + q : "Search",
|
|
573
|
+
kind: "search"
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
case "list_dir":
|
|
577
|
+
case "ls": {
|
|
578
|
+
const path = pick$1(input, "path", "directory", "Path", "Directory", "SearchDirectory", "AbsolutePath") ?? "";
|
|
579
|
+
return {
|
|
580
|
+
card: "generic",
|
|
581
|
+
title: path !== "" ? "List " + path : "List directory"
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
case "delete_file":
|
|
585
|
+
case "remove_file":
|
|
586
|
+
case "rm": return {
|
|
587
|
+
card: "generic",
|
|
588
|
+
title: "Delete " + (pick$1(input, "path", "file_path", "Path", "FilePath", "AbsolutePath") ?? ""),
|
|
589
|
+
kind: "delete"
|
|
590
|
+
};
|
|
591
|
+
default: return {
|
|
592
|
+
card: "generic",
|
|
593
|
+
title: name !== "" ? name : "agy tool",
|
|
594
|
+
rawInput: input
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
/** Native completed-card projection for one mirrored agy tool call. */
|
|
599
|
+
function presentMirrorResult(args, result) {
|
|
600
|
+
const a = args;
|
|
601
|
+
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
602
|
+
const text = resultText$1(result.content);
|
|
603
|
+
switch (name) {
|
|
604
|
+
case "run_command":
|
|
605
|
+
case "bash":
|
|
606
|
+
case "execute_command": return {
|
|
607
|
+
card: "terminal",
|
|
608
|
+
output: text
|
|
609
|
+
};
|
|
610
|
+
case "write_to_file":
|
|
611
|
+
case "write_file":
|
|
612
|
+
case "create_file":
|
|
613
|
+
case "edit_file":
|
|
614
|
+
case "replace_in_file":
|
|
615
|
+
case "edit": {
|
|
616
|
+
const call = presentMirrorCall(args);
|
|
617
|
+
return {
|
|
618
|
+
card: "diff",
|
|
619
|
+
diffs: call !== void 0 && call.card === "diff" ? call.diffs : []
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
default: return {
|
|
623
|
+
card: "generic",
|
|
624
|
+
content: [{
|
|
625
|
+
type: "text",
|
|
626
|
+
text: clip(text, 4e3)
|
|
627
|
+
}]
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function resultText$1(content) {
|
|
632
|
+
const parts = [];
|
|
633
|
+
for (const b of Array.isArray(content) ? content : []) {
|
|
634
|
+
const blk = b;
|
|
635
|
+
if (blk && blk.type === "text" && typeof blk.text === "string") parts.push(blk.text);
|
|
636
|
+
}
|
|
637
|
+
return parts.join("\n");
|
|
638
|
+
}
|
|
639
|
+
function clip(s, max) {
|
|
640
|
+
return s.length > max ? s.slice(0, max) + "โฆ (+" + (s.length - max) + " chars)" : s;
|
|
641
|
+
}
|
|
642
|
+
function defineAgyMirrorTool(deps) {
|
|
643
|
+
const enrichArgs = (args) => {
|
|
644
|
+
if (typeof args.tool === "string" && args.tool !== "") return args;
|
|
645
|
+
const runId = typeof args.run === "string" ? args.run : "";
|
|
646
|
+
const step = typeof args.step === "number" ? args.step : -1;
|
|
647
|
+
const t = deps.runs.get(runId)?.toolEventAt(step) ?? null;
|
|
648
|
+
if (t === null) return args;
|
|
649
|
+
return {
|
|
650
|
+
...args,
|
|
651
|
+
tool: t.name,
|
|
652
|
+
...args.input === void 0 && t.args !== void 0 ? { input: t.args } : {}
|
|
653
|
+
};
|
|
654
|
+
};
|
|
655
|
+
return defineTool({
|
|
656
|
+
name: MIRROR_TOOL_NAME,
|
|
657
|
+
description: "Internal to the dsh-agy-link bridge: replays one tool activity recorded from a Google Antigravity (agy) CLI run so it renders as a native tool card and rides the agent loop. Emitted automatically by the antigravity provider โ do not call it directly.",
|
|
658
|
+
parameters: {
|
|
659
|
+
run: {
|
|
660
|
+
type: "string",
|
|
661
|
+
required: true,
|
|
662
|
+
description: "Recording run id."
|
|
663
|
+
},
|
|
664
|
+
step: {
|
|
665
|
+
type: "number",
|
|
666
|
+
required: true,
|
|
667
|
+
description: "Recorded event index of the tool step."
|
|
668
|
+
},
|
|
669
|
+
tool: {
|
|
670
|
+
type: "string",
|
|
671
|
+
description: "agy tool name that ran (optional โ resolved from the recording)."
|
|
672
|
+
},
|
|
673
|
+
input: {
|
|
674
|
+
type: "json",
|
|
675
|
+
description: "agy tool arguments as recorded (optional โ resolved from the recording)."
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
output: {
|
|
679
|
+
schema: { type: "string" },
|
|
680
|
+
render: (_args, value) => [{
|
|
681
|
+
type: "text",
|
|
682
|
+
text: value
|
|
683
|
+
}]
|
|
684
|
+
},
|
|
685
|
+
presentCall: (args) => presentMirrorCall(enrichArgs(args)),
|
|
686
|
+
presentResult: (args, result) => presentMirrorResult(enrichArgs(args), result),
|
|
687
|
+
async execute(args, exec) {
|
|
688
|
+
exec.signal;
|
|
689
|
+
const runId = typeof args.run === "string" ? args.run : "";
|
|
690
|
+
const step = typeof args.step === "number" ? args.step : -1;
|
|
691
|
+
typeof args.tool === "string" && args.tool;
|
|
692
|
+
const rec = deps.runs.get(runId);
|
|
693
|
+
if (rec === void 0) throw new Error("agy_tool: no recorded agy run \"" + runId + "\" โ this tool only replays bridge-recorded activity");
|
|
694
|
+
const t = rec.toolEventAt(step);
|
|
695
|
+
if (t === null) throw new Error("agy_tool: event " + step + " of run " + runId + " is not a completed tool step");
|
|
696
|
+
if (t.error !== void 0) throw new Error("agy tool " + t.name + " failed: " + t.error);
|
|
697
|
+
const out = t.output;
|
|
698
|
+
if (out === void 0 || out === null) return "";
|
|
699
|
+
if (typeof out === "string") return out;
|
|
700
|
+
try {
|
|
701
|
+
return JSON.stringify(out, null, 2);
|
|
702
|
+
} catch {
|
|
703
|
+
return String(out);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
//#endregion
|
|
454
709
|
//#region src/host/mapper.ts
|
|
455
710
|
function usageFromRaw(raw) {
|
|
456
711
|
const usage = {
|
|
@@ -543,12 +798,12 @@ var EventMapper = class {
|
|
|
543
798
|
if (ev.kind === "step") {
|
|
544
799
|
if (ev.stepKind === "text") {
|
|
545
800
|
const thoughtTokens = ev.usage?.thinking_tokens ?? 0;
|
|
546
|
-
|
|
801
|
+
const stepTextEmitted = (this.emittedByKey.get(ev.stepKey) ?? "") !== "";
|
|
802
|
+
if (thoughtTokens > 0 && !stepTextEmitted && !this.thinkingAnnounced.has(ev.stepKey)) {
|
|
547
803
|
this.thinkingAnnounced.add(ev.stepKey);
|
|
548
804
|
yield* this.ensureBlock("reasoning");
|
|
549
805
|
const d = this.appendDelta("[agy thinking turn ยท " + thoughtTokens + " thinking tokens]\n");
|
|
550
806
|
if (d) yield d;
|
|
551
|
-
return;
|
|
552
807
|
}
|
|
553
808
|
if (ev.text === "" && !ev.fragment) return;
|
|
554
809
|
this.sawTextStep = true;
|
|
@@ -587,13 +842,7 @@ var EventMapper = class {
|
|
|
587
842
|
const close = this.closeOpen();
|
|
588
843
|
if (close) yield close;
|
|
589
844
|
const idx = this.blockIdx;
|
|
590
|
-
const
|
|
591
|
-
const argumentsJson = JSON.stringify({
|
|
592
|
-
run: this.opts.runId,
|
|
593
|
-
step: absIndex,
|
|
594
|
-
tool: ev.tool.name,
|
|
595
|
-
...input
|
|
596
|
-
});
|
|
845
|
+
const argumentsJson = JSON.stringify(buildMirrorRunCode(this.opts.runId, absIndex, ev.tool.name));
|
|
597
846
|
yield {
|
|
598
847
|
type: "block-start",
|
|
599
848
|
index: idx,
|
|
@@ -605,7 +854,7 @@ var EventMapper = class {
|
|
|
605
854
|
block: {
|
|
606
855
|
type: "tool-call",
|
|
607
856
|
id: CallId(mirrorCallId(this.opts.runId, absIndex)),
|
|
608
|
-
name:
|
|
857
|
+
name: WRAPPER_TOOL_NAME,
|
|
609
858
|
arguments: argumentsJson
|
|
610
859
|
}
|
|
611
860
|
};
|
|
@@ -2621,222 +2870,6 @@ function writeDoctorReport(deps) {
|
|
|
2621
2870
|
return file;
|
|
2622
2871
|
}
|
|
2623
2872
|
//#endregion
|
|
2624
|
-
//#region src/host/mirror-tool.ts
|
|
2625
|
-
const MIRROR_TOOL_NAME = "agy_tool";
|
|
2626
|
-
/** agy serializes some tool args as a JSON string; presenters get an object. */
|
|
2627
|
-
function toolInput(args) {
|
|
2628
|
-
const raw = args.input;
|
|
2629
|
-
if (raw === void 0 || raw === null) return {};
|
|
2630
|
-
if (typeof raw === "object") return raw;
|
|
2631
|
-
if (typeof raw === "string") try {
|
|
2632
|
-
const parsed = JSON.parse(raw);
|
|
2633
|
-
return typeof parsed === "object" && parsed !== null ? parsed : { value: parsed };
|
|
2634
|
-
} catch {
|
|
2635
|
-
return { value: raw };
|
|
2636
|
-
}
|
|
2637
|
-
return { value: raw };
|
|
2638
|
-
}
|
|
2639
|
-
function asString(v) {
|
|
2640
|
-
return typeof v === "string" ? v : void 0;
|
|
2641
|
-
}
|
|
2642
|
-
/** Native pending-card projection for one mirrored agy tool call. */
|
|
2643
|
-
function presentMirrorCall(args) {
|
|
2644
|
-
const a = args;
|
|
2645
|
-
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
2646
|
-
const input = toolInput(a);
|
|
2647
|
-
switch (name) {
|
|
2648
|
-
case "run_command":
|
|
2649
|
-
case "bash":
|
|
2650
|
-
case "execute_command": return {
|
|
2651
|
-
card: "terminal",
|
|
2652
|
-
title: asString(input.command) ?? asString(input.cmd) ?? JSON.stringify(input),
|
|
2653
|
-
...asString(input.description) !== void 0 ? { description: asString(input.description) } : {},
|
|
2654
|
-
...asString(input.cwd) !== void 0 ? { cwd: asString(input.cwd) } : {}
|
|
2655
|
-
};
|
|
2656
|
-
case "write_to_file":
|
|
2657
|
-
case "write_file":
|
|
2658
|
-
case "create_file": {
|
|
2659
|
-
const path = asString(input.path) ?? asString(input.file_path) ?? asString(input.filename) ?? "file";
|
|
2660
|
-
const content = asString(input.content) ?? "";
|
|
2661
|
-
return {
|
|
2662
|
-
card: "diff",
|
|
2663
|
-
title: "Write " + path,
|
|
2664
|
-
diffs: [{
|
|
2665
|
-
path,
|
|
2666
|
-
oldText: null,
|
|
2667
|
-
newText: content
|
|
2668
|
-
}],
|
|
2669
|
-
locations: [{ path }]
|
|
2670
|
-
};
|
|
2671
|
-
}
|
|
2672
|
-
case "edit_file":
|
|
2673
|
-
case "replace_in_file":
|
|
2674
|
-
case "edit": {
|
|
2675
|
-
const path = asString(input.path) ?? asString(input.file_path) ?? "file";
|
|
2676
|
-
const newText = asString(input.new_string) ?? asString(input.newText) ?? asString(input.content) ?? "";
|
|
2677
|
-
const oldText = asString(input.old_string) ?? asString(input.oldText) ?? null;
|
|
2678
|
-
return {
|
|
2679
|
-
card: "diff",
|
|
2680
|
-
title: "Edit " + path,
|
|
2681
|
-
diffs: [{
|
|
2682
|
-
path,
|
|
2683
|
-
oldText,
|
|
2684
|
-
newText
|
|
2685
|
-
}],
|
|
2686
|
-
locations: [{ path }]
|
|
2687
|
-
};
|
|
2688
|
-
}
|
|
2689
|
-
case "read_file":
|
|
2690
|
-
case "view_file":
|
|
2691
|
-
case "read":
|
|
2692
|
-
case "open_file": {
|
|
2693
|
-
const path = asString(input.path) ?? asString(input.file_path) ?? asString(input.filename) ?? "";
|
|
2694
|
-
const offset = typeof input.offset === "number" ? input.offset : void 0;
|
|
2695
|
-
return {
|
|
2696
|
-
card: "generic",
|
|
2697
|
-
title: "Read " + path,
|
|
2698
|
-
kind: "read",
|
|
2699
|
-
...path !== "" ? { locations: [{
|
|
2700
|
-
path,
|
|
2701
|
-
...offset !== void 0 ? { line: offset + 1 } : {}
|
|
2702
|
-
}] } : {}
|
|
2703
|
-
};
|
|
2704
|
-
}
|
|
2705
|
-
case "find_by_name":
|
|
2706
|
-
case "glob":
|
|
2707
|
-
case "search_files":
|
|
2708
|
-
case "search":
|
|
2709
|
-
case "search_file_content":
|
|
2710
|
-
case "grep": {
|
|
2711
|
-
const q = asString(input.pattern) ?? asString(input.query) ?? asString(input.regex) ?? "";
|
|
2712
|
-
return {
|
|
2713
|
-
card: "generic",
|
|
2714
|
-
title: q !== "" ? "Search " + q : "Search",
|
|
2715
|
-
kind: "search"
|
|
2716
|
-
};
|
|
2717
|
-
}
|
|
2718
|
-
case "list_dir":
|
|
2719
|
-
case "ls": {
|
|
2720
|
-
const path = asString(input.path) ?? asString(input.directory) ?? "";
|
|
2721
|
-
return {
|
|
2722
|
-
card: "generic",
|
|
2723
|
-
title: path !== "" ? "List " + path : "List directory"
|
|
2724
|
-
};
|
|
2725
|
-
}
|
|
2726
|
-
case "delete_file":
|
|
2727
|
-
case "remove_file":
|
|
2728
|
-
case "rm": return {
|
|
2729
|
-
card: "generic",
|
|
2730
|
-
title: "Delete " + (asString(input.path) ?? asString(input.file_path) ?? ""),
|
|
2731
|
-
kind: "delete"
|
|
2732
|
-
};
|
|
2733
|
-
default: return {
|
|
2734
|
-
card: "generic",
|
|
2735
|
-
title: name !== "" ? name : "agy tool",
|
|
2736
|
-
rawInput: input
|
|
2737
|
-
};
|
|
2738
|
-
}
|
|
2739
|
-
}
|
|
2740
|
-
/** Native completed-card projection for one mirrored agy tool call. */
|
|
2741
|
-
function presentMirrorResult(args, result) {
|
|
2742
|
-
const a = args;
|
|
2743
|
-
const name = typeof a?.tool === "string" ? a.tool : "";
|
|
2744
|
-
const text = resultText$1(result.content);
|
|
2745
|
-
switch (name) {
|
|
2746
|
-
case "run_command":
|
|
2747
|
-
case "bash":
|
|
2748
|
-
case "execute_command": return {
|
|
2749
|
-
card: "terminal",
|
|
2750
|
-
output: text
|
|
2751
|
-
};
|
|
2752
|
-
case "write_to_file":
|
|
2753
|
-
case "write_file":
|
|
2754
|
-
case "create_file":
|
|
2755
|
-
case "edit_file":
|
|
2756
|
-
case "replace_in_file":
|
|
2757
|
-
case "edit": {
|
|
2758
|
-
const call = presentMirrorCall(args);
|
|
2759
|
-
return {
|
|
2760
|
-
card: "diff",
|
|
2761
|
-
diffs: call !== void 0 && call.card === "diff" ? call.diffs : []
|
|
2762
|
-
};
|
|
2763
|
-
}
|
|
2764
|
-
default: return {
|
|
2765
|
-
card: "generic",
|
|
2766
|
-
content: [{
|
|
2767
|
-
type: "text",
|
|
2768
|
-
text: clip(text, 4e3)
|
|
2769
|
-
}]
|
|
2770
|
-
};
|
|
2771
|
-
}
|
|
2772
|
-
}
|
|
2773
|
-
function resultText$1(content) {
|
|
2774
|
-
const parts = [];
|
|
2775
|
-
for (const b of Array.isArray(content) ? content : []) {
|
|
2776
|
-
const blk = b;
|
|
2777
|
-
if (blk && blk.type === "text" && typeof blk.text === "string") parts.push(blk.text);
|
|
2778
|
-
}
|
|
2779
|
-
return parts.join("\n");
|
|
2780
|
-
}
|
|
2781
|
-
function clip(s, max) {
|
|
2782
|
-
return s.length > max ? s.slice(0, max) + "โฆ (+" + (s.length - max) + " chars)" : s;
|
|
2783
|
-
}
|
|
2784
|
-
function defineAgyMirrorTool(deps) {
|
|
2785
|
-
return defineTool({
|
|
2786
|
-
name: MIRROR_TOOL_NAME,
|
|
2787
|
-
description: "Internal to the dsh-agy-link bridge: replays one tool activity recorded from a Google Antigravity (agy) CLI run so it renders as a native tool card and rides the agent loop. Emitted automatically by the antigravity provider โ do not call it directly.",
|
|
2788
|
-
parameters: {
|
|
2789
|
-
run: {
|
|
2790
|
-
type: "string",
|
|
2791
|
-
required: true,
|
|
2792
|
-
description: "Recording run id."
|
|
2793
|
-
},
|
|
2794
|
-
step: {
|
|
2795
|
-
type: "number",
|
|
2796
|
-
required: true,
|
|
2797
|
-
description: "Recorded event index of the tool step."
|
|
2798
|
-
},
|
|
2799
|
-
tool: {
|
|
2800
|
-
type: "string",
|
|
2801
|
-
required: true,
|
|
2802
|
-
description: "agy tool name that ran."
|
|
2803
|
-
},
|
|
2804
|
-
input: {
|
|
2805
|
-
type: "json",
|
|
2806
|
-
description: "agy tool arguments as recorded."
|
|
2807
|
-
}
|
|
2808
|
-
},
|
|
2809
|
-
output: {
|
|
2810
|
-
schema: { type: "string" },
|
|
2811
|
-
render: (_args, value) => [{
|
|
2812
|
-
type: "text",
|
|
2813
|
-
text: value
|
|
2814
|
-
}]
|
|
2815
|
-
},
|
|
2816
|
-
presentCall: (args) => presentMirrorCall(args),
|
|
2817
|
-
presentResult: (args, result) => presentMirrorResult(args, result),
|
|
2818
|
-
async execute(args, exec) {
|
|
2819
|
-
exec.signal;
|
|
2820
|
-
const runId = typeof args.run === "string" ? args.run : "";
|
|
2821
|
-
const step = typeof args.step === "number" ? args.step : -1;
|
|
2822
|
-
typeof args.tool === "string" && args.tool;
|
|
2823
|
-
const rec = deps.runs.get(runId);
|
|
2824
|
-
if (rec === void 0) throw new Error("agy_tool: no recorded agy run \"" + runId + "\" โ this tool only replays bridge-recorded activity");
|
|
2825
|
-
const t = rec.toolEventAt(step);
|
|
2826
|
-
if (t === null) throw new Error("agy_tool: event " + step + " of run " + runId + " is not a completed tool step");
|
|
2827
|
-
if (t.error !== void 0) throw new Error("agy tool " + t.name + " failed: " + t.error);
|
|
2828
|
-
const out = t.output;
|
|
2829
|
-
if (out === void 0 || out === null) return "";
|
|
2830
|
-
if (typeof out === "string") return out;
|
|
2831
|
-
try {
|
|
2832
|
-
return JSON.stringify(out, null, 2);
|
|
2833
|
-
} catch {
|
|
2834
|
-
return String(out);
|
|
2835
|
-
}
|
|
2836
|
-
}
|
|
2837
|
-
});
|
|
2838
|
-
}
|
|
2839
|
-
//#endregion
|
|
2840
2873
|
//#region src/host/sessions.ts
|
|
2841
2874
|
var SessionStore = class {
|
|
2842
2875
|
file;
|
|
@@ -3215,12 +3248,13 @@ function apply(ctx, entryConfig = {}) {
|
|
|
3215
3248
|
});
|
|
3216
3249
|
}
|
|
3217
3250
|
}));
|
|
3218
|
-
const
|
|
3251
|
+
const toolsSvcRef = { current: null };
|
|
3219
3252
|
const askToolDispose = { current: null };
|
|
3253
|
+
const mirrorToolDispose = { current: null };
|
|
3220
3254
|
const syncAskTool = () => {
|
|
3221
3255
|
const want = getConfig().askTool && bin() !== null;
|
|
3222
|
-
if (want && askToolDispose.current === null &&
|
|
3223
|
-
const reg =
|
|
3256
|
+
if (want && askToolDispose.current === null && toolsSvcRef.current) {
|
|
3257
|
+
const reg = toolsSvcRef.current.register(defineAgyAskTool({
|
|
3224
3258
|
cfg: getConfig,
|
|
3225
3259
|
bin,
|
|
3226
3260
|
catalog: () => catalog.get()
|
|
@@ -3231,19 +3265,29 @@ function apply(ctx, entryConfig = {}) {
|
|
|
3231
3265
|
askToolDispose.current = null;
|
|
3232
3266
|
}
|
|
3233
3267
|
};
|
|
3234
|
-
syncAskTool();
|
|
3235
|
-
const mirrorToolDispose = { current: null };
|
|
3236
3268
|
const syncMirrorTool = () => {
|
|
3237
3269
|
const want = getConfig().enabled && bin() !== null;
|
|
3238
|
-
if (want && mirrorToolDispose.current === null &&
|
|
3239
|
-
const reg =
|
|
3270
|
+
if (want && mirrorToolDispose.current === null && toolsSvcRef.current) {
|
|
3271
|
+
const reg = toolsSvcRef.current.register(defineAgyMirrorTool({ runs }));
|
|
3240
3272
|
mirrorToolDispose.current = typeof reg === "function" ? reg : null;
|
|
3273
|
+
if (mirrorToolDispose.current !== null) log("agy_tool mirror registered with the tools service");
|
|
3241
3274
|
} else if (!want && mirrorToolDispose.current !== null) {
|
|
3242
3275
|
mirrorToolDispose.current();
|
|
3243
3276
|
mirrorToolDispose.current = null;
|
|
3244
3277
|
}
|
|
3245
3278
|
};
|
|
3246
|
-
|
|
3279
|
+
ctx.inject(["tools"], (sub) => {
|
|
3280
|
+
toolsSvcRef.current = sub.get("tools");
|
|
3281
|
+
syncAskTool();
|
|
3282
|
+
syncMirrorTool();
|
|
3283
|
+
return () => {
|
|
3284
|
+
if (askToolDispose.current !== null) askToolDispose.current();
|
|
3285
|
+
askToolDispose.current = null;
|
|
3286
|
+
if (mirrorToolDispose.current !== null) mirrorToolDispose.current();
|
|
3287
|
+
mirrorToolDispose.current = null;
|
|
3288
|
+
toolsSvcRef.current = null;
|
|
3289
|
+
};
|
|
3290
|
+
});
|
|
3247
3291
|
const registerRoutes = (webServer) => {
|
|
3248
3292
|
const disposers = [];
|
|
3249
3293
|
const reg = (route) => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-agy-link",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "Google Antigravity (agy CLI) models for DeepSeek Harness โ stream Gemini/Claude/GPT-OSS subscriptions into DSH with thinking, tool activity, token usage and in-GUI Google OAuth login.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|