Skip to content

Commit 723d582

Browse files
xesrevinuGit Agent
andcommitted
feat(shared): enhance output and progress tracing
- Added rendering enhancements for commit output. - Introduced progress tracking features in tracing module. - Created new config for progress rendering attributes. These updates improve the commit output formatting and add new progress tracking capabilities. The rendering logic for commit results is cleaned up, and precise control over attribute visibility is implemented in the progress module. Co-Authored-By: Git Agent <noreply@git-agent.dev>
1 parent da0da6b commit 723d582

3 files changed

Lines changed: 1030 additions & 9 deletions

File tree

src/shared/output.ts

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,39 @@
11
import { Console, Effect } from "effect";
22
import type { SingleCommitResult } from "../domain/commit";
33

4+
const indent = (value: string, prefix = " "): string =>
5+
value
6+
.split("\n")
7+
.map((line) => `${prefix}${line}`)
8+
.join("\n");
9+
10+
const renderFiles = (files: ReadonlyArray<string>): string =>
11+
files.length === 0 ? "Files: none" : `Files: ${files.join(", ")}`;
12+
13+
const renderCommitBlock = (commit: SingleCommitResult, index: number): string => {
14+
const sections = [`${index + 1}. ${commit.title}`, indent(renderFiles(commit.files))];
15+
16+
for (const bullet of commit.bullets) {
17+
sections.push(indent(`- ${bullet}`));
18+
}
19+
20+
if (commit.explanation.trim().length > 0) {
21+
sections.push(indent(commit.explanation.trim()));
22+
}
23+
24+
if (commit.output?.trim().length) {
25+
sections.push(indent(commit.output.trim()));
26+
}
27+
28+
return sections.join("\n");
29+
};
30+
431
export const printDryRunResult = (commits: ReadonlyArray<SingleCommitResult>) =>
5-
Effect.forEach(commits, (commit, index) =>
6-
Console.log(`${index + 1}. ${commit.title}\n ${commit.files.join(", ")}`),
7-
);
32+
Effect.forEach(commits, (commit, index) => Console.log(renderCommitBlock(commit, index)));
833

934
export const printCommitResult = (commits: ReadonlyArray<SingleCommitResult>) =>
10-
Effect.forEach(commits, (commit) =>
11-
Console.log(
12-
[commit.output?.trim(), commit.explanation.trim()]
13-
.filter((value) => value != null && value.length > 0)
14-
.join("\n"),
15-
),
35+
Console.log(
36+
[`Created ${commits.length} commit${commits.length === 1 ? "" : "s"}.`, ""]
37+
.concat(commits.map((commit, index) => renderCommitBlock(commit, index)).join("\n\n"))
38+
.join("\n"),
1639
);

src/shared/progress-config.ts

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
import type { ProgressAttributeDescriptor, ProgressRenderConfig } from "./tracing";
2+
3+
const hiddenAttributePrefixes = ["http.request.header.", "http.response.header."];
4+
5+
const hiddenInteractiveAttributeKeys = new Set([
6+
"concurrency",
7+
"gen_ai.openai.request.response_format",
8+
"gen_ai.openai.response.service_tier",
9+
"gen_ai.operation.name",
10+
"gen_ai.response.id",
11+
"gen_ai.system",
12+
"server.port",
13+
"url.full",
14+
]);
15+
16+
const orderedInteractiveAttributeKeys = [
17+
"vcs",
18+
"requested_vcs",
19+
"dry_run",
20+
"no_stage",
21+
"amend",
22+
"step",
23+
"reason",
24+
"full_wizard",
25+
"hook_count",
26+
"max_commits",
27+
"staged_files",
28+
"unstaged_files",
29+
"file_count",
30+
"http.request.method",
31+
"url.path",
32+
"http.response.status_code",
33+
"server.address",
34+
"gen_ai.request.model",
35+
"gen_ai.response.model",
36+
] as const;
37+
38+
const interactiveLabelByKey: Record<string, string> = {
39+
file_count: "files",
40+
hook_count: "hooks",
41+
max_commits: "commits",
42+
staged_files: "staged",
43+
unstaged_files: "unstaged",
44+
"gen_ai.request.model": "model",
45+
"gen_ai.response.model": "model",
46+
"http.request.method": "method",
47+
"http.response.status_code": "status",
48+
"server.address": "server",
49+
"url.path": "path",
50+
};
51+
52+
const toInteractiveDescriptors = (
53+
attributes: ReadonlyMap<string, unknown>,
54+
): Array<ProgressAttributeDescriptor> => {
55+
const descriptors: Array<ProgressAttributeDescriptor> = [];
56+
const used = new Set<string>();
57+
58+
const push = (
59+
key: string,
60+
value: unknown,
61+
label = interactiveLabelByKey[key] ?? key,
62+
dedupeKey?: string,
63+
) => {
64+
if (value === undefined || value === null) {
65+
return;
66+
}
67+
used.add(key);
68+
descriptors.push(
69+
dedupeKey == null
70+
? {
71+
key,
72+
label,
73+
value,
74+
}
75+
: {
76+
key,
77+
label,
78+
value,
79+
dedupeKey,
80+
},
81+
);
82+
};
83+
84+
const groupIndex = attributes.get("group_index");
85+
const groupTotal = attributes.get("group_total");
86+
if (groupIndex !== undefined && groupTotal !== undefined) {
87+
used.add("group_index");
88+
used.add("group_total");
89+
descriptors.push({
90+
key: "group_index",
91+
label: "group",
92+
value: `${groupIndex}/${groupTotal}`,
93+
dedupeKey: `group=${groupIndex}/${groupTotal}`,
94+
});
95+
}
96+
97+
const inputTokens = attributes.get("gen_ai.usage.input_tokens");
98+
const outputTokens = attributes.get("gen_ai.usage.output_tokens");
99+
if (inputTokens !== undefined || outputTokens !== undefined) {
100+
used.add("gen_ai.usage.input_tokens");
101+
used.add("gen_ai.usage.output_tokens");
102+
descriptors.push({
103+
key: "gen_ai.usage.input_tokens",
104+
label: "tokens",
105+
value: `${inputTokens ?? 0}/${outputTokens ?? 0}`,
106+
dedupeKey: `tokens=${inputTokens ?? 0}/${outputTokens ?? 0}`,
107+
});
108+
}
109+
110+
for (const key of orderedInteractiveAttributeKeys) {
111+
if (used.has(key) || !attributes.has(key)) {
112+
continue;
113+
}
114+
push(key, attributes.get(key));
115+
}
116+
117+
const remaining = [...attributes.entries()]
118+
.filter(([key, value]) => {
119+
if (used.has(key) || value === undefined || value === null) {
120+
return false;
121+
}
122+
if (hiddenInteractiveAttributeKeys.has(key)) {
123+
return false;
124+
}
125+
return hiddenAttributePrefixes.every((prefix) => !key.startsWith(prefix));
126+
})
127+
.sort(([left], [right]) => left.localeCompare(right));
128+
129+
for (const [key, value] of remaining) {
130+
push(key, value);
131+
}
132+
133+
return descriptors;
134+
};
135+
136+
export const gitAgentProgressRenderConfig: ProgressRenderConfig = {
137+
headerLabel: "Git agent",
138+
formatAttributes({ attributes }, { mode }) {
139+
if (mode === "raw") {
140+
return undefined;
141+
}
142+
return toInteractiveDescriptors(attributes);
143+
},
144+
};

0 commit comments

Comments
 (0)