Skip to content

Commit 5f968c0

Browse files
xesrevinuGit Agent
andcommitted
feat(shared): enhance error handling and attributes
- Add CommitPlanError for specific error handling. - Refactor process execution error handling logic. - Improve attribute configuration in progress tracking. This update enhances the error handling by introducing a new CommitPlanError class, refactors how process execution errors are handled, and improves the attribute configuration in the progress tracking module for better clarity and usability. Co-Authored-By: Git Agent <noreply@git-agent.dev>
1 parent 686100d commit 5f968c0

6 files changed

Lines changed: 437 additions & 309 deletions

File tree

src/shared/errors.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,10 @@ export class HookBlockedError extends Schema.TaggedErrorClass<HookBlockedError>(
3333
},
3434
) {}
3535

36+
export class CommitPlanError extends Schema.TaggedErrorClass<CommitPlanError>()("CommitPlanError", {
37+
message: Schema.String,
38+
}) {}
39+
3640
export class UnsupportedFeatureError extends Schema.TaggedErrorClass<UnsupportedFeatureError>()(
3741
"UnsupportedFeatureError",
3842
{
@@ -71,6 +75,9 @@ export const renderError = (error: unknown): string => {
7175
}
7276
return lines.join("\n");
7377
}
78+
if (error instanceof CommitPlanError) {
79+
return error.message;
80+
}
7481
if (error instanceof UnsupportedFeatureError) {
7582
return error.message;
7683
}

src/shared/process.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -58,22 +58,20 @@ export const runProcess = ({
5858
} satisfies ProcessResult;
5959

6060
if (result.exitCode !== 0 && !allowFailure) {
61-
return yield* Effect.fail(
62-
new ProcessExecutionError({
63-
command: renderCommand(command, args),
64-
exitCode: result.exitCode,
65-
stdout: result.stdout,
66-
stderr: result.stderr,
67-
}),
68-
);
61+
return yield* new ProcessExecutionError({
62+
command: renderCommand(command, args),
63+
exitCode: result.exitCode,
64+
stdout: result.stdout,
65+
stderr: result.stderr,
66+
});
6967
}
7068

7169
return result;
7270
}).pipe(
7371
Effect.catch((cause) =>
7472
cause instanceof ProcessExecutionError
75-
? Effect.fail(cause)
76-
: Effect.fail(toProcessExecutionError(command, args, cause)),
73+
? Effect.failSync(() => cause)
74+
: Effect.failSync(() => toProcessExecutionError(command, args, cause)),
7775
),
7876
),
7977
);

src/shared/progress-config.ts

Lines changed: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ import type { ProgressAttributeDescriptor, ProgressRenderConfig } from "./tracin
22

33
const hiddenAttributePrefixes = ["http.request.header.", "http.response.header."];
44

5-
const hiddenInteractiveAttributeKeys = new Set([
5+
const hiddenAttributeKeys = new Set([
66
"concurrency",
77
"gen_ai.openai.request.response_format",
88
"gen_ai.openai.response.service_tier",
99
"gen_ai.operation.name",
1010
"gen_ai.response.id",
1111
"gen_ai.system",
12+
"http.request.method",
1213
"server.port",
14+
"toolChoice",
1315
"url.full",
16+
"url.scheme",
1417
]);
1518

1619
const orderedInteractiveAttributeKeys = [
@@ -27,7 +30,6 @@ const orderedInteractiveAttributeKeys = [
2730
"staged_files",
2831
"unstaged_files",
2932
"file_count",
30-
"http.request.method",
3133
"url.path",
3234
"http.response.status_code",
3335
"server.address",
@@ -43,13 +45,86 @@ const interactiveLabelByKey: Record<string, string> = {
4345
unstaged_files: "unstaged",
4446
"gen_ai.request.model": "model",
4547
"gen_ai.response.model": "model",
46-
"http.request.method": "method",
4748
"http.response.status_code": "status",
4849
"server.address": "server",
4950
"url.path": "path",
5051
};
5152

52-
const toInteractiveDescriptors = (
53+
const friendlySpanNames: Record<string, string> = {
54+
"commit.prepare-request": "Prepare commit",
55+
"commit.run": "Run commit",
56+
"commit.resolve-provider": "Resolve provider",
57+
"commit.load-project-config": "Load project config",
58+
"commit.scan-changes": "Scan changes",
59+
"commit.plan-groups": "Plan commits",
60+
"commit.refresh-scopes": "Refresh scopes",
61+
"commit.replan-groups": "Replan commits",
62+
"commit.generate-message": "Generate commit message",
63+
"commit.run-hooks": "Run commit hooks",
64+
"commit.create": "Create commit",
65+
"commit.load-previous": "Load previous commit",
66+
"commit.generate-amend-message": "Generate amended message",
67+
"commit.amend": "Amend commit",
68+
"init.run": "Run init",
69+
"init.resolve-provider": "Resolve provider",
70+
"init.initialize-repository": "Initialize repository",
71+
"init.generate-gitignore": "Generate .gitignore",
72+
"init.generate-scopes": "Generate scopes",
73+
"init.write-default-hook": "Write default hook",
74+
"init.write-hook": "Write hook config",
75+
"init.write-project-config": "Write project config",
76+
"config.resolve-provider": "Resolve provider config",
77+
"config.resolve-field": "Resolve config value",
78+
"hooks.execute": "Run hook",
79+
"LanguageModel.generateText": "Call model",
80+
};
81+
82+
const titleCase = (value: string): string =>
83+
value
84+
.split(/\s+/)
85+
.filter((part) => part.length > 0)
86+
.map((part) => part[0]?.toUpperCase() + part.slice(1))
87+
.join(" ");
88+
89+
const formatFriendlySpanName = (name: string, attributes: ReadonlyMap<string, unknown>): string => {
90+
const friendly = friendlySpanNames[name];
91+
if (friendly != null) {
92+
if (name === "hooks.execute") {
93+
const hookType = attributes.get("hook_type");
94+
return hookType === "conventional" ? "Validate conventional commit" : friendly;
95+
}
96+
return friendly;
97+
}
98+
99+
if (name.startsWith("http.client")) {
100+
const method = attributes.get("http.request.method");
101+
return typeof method === "string" && method.length > 0 ? `HTTP ${method}` : "HTTP request";
102+
}
103+
104+
return titleCase(name.replace(/[._-]+/g, " "));
105+
};
106+
107+
const simplifyInteractiveLabel = (key: string): string => {
108+
const mapped = interactiveLabelByKey[key];
109+
if (mapped != null) {
110+
return mapped;
111+
}
112+
if (key.startsWith("gen_ai.request.")) {
113+
return key.slice("gen_ai.request.".length);
114+
}
115+
if (key.startsWith("gen_ai.response.")) {
116+
return key.slice("gen_ai.response.".length);
117+
}
118+
if (key.startsWith("gen_ai.usage.")) {
119+
return key.slice("gen_ai.usage.".length);
120+
}
121+
if (key.startsWith("gen_ai.")) {
122+
return key.slice("gen_ai.".length);
123+
}
124+
return key;
125+
};
126+
127+
const toDescriptors = (
53128
attributes: ReadonlyMap<string, unknown>,
54129
): Array<ProgressAttributeDescriptor> => {
55130
const descriptors: Array<ProgressAttributeDescriptor> = [];
@@ -58,7 +133,7 @@ const toInteractiveDescriptors = (
58133
const push = (
59134
key: string,
60135
value: unknown,
61-
label = interactiveLabelByKey[key] ?? key,
136+
label = simplifyInteractiveLabel(key),
62137
dedupeKey?: string,
63138
) => {
64139
if (value === undefined || value === null) {
@@ -119,7 +194,7 @@ const toInteractiveDescriptors = (
119194
if (used.has(key) || value === undefined || value === null) {
120195
return false;
121196
}
122-
if (hiddenInteractiveAttributeKeys.has(key)) {
197+
if (hiddenAttributeKeys.has(key)) {
123198
return false;
124199
}
125200
return hiddenAttributePrefixes.every((prefix) => !key.startsWith(prefix));
@@ -135,10 +210,10 @@ const toInteractiveDescriptors = (
135210

136211
export const gitAgentProgressRenderConfig: ProgressRenderConfig = {
137212
headerLabel: "Git agent",
138-
formatAttributes({ attributes }, { mode }) {
139-
if (mode === "raw") {
140-
return undefined;
141-
}
142-
return toInteractiveDescriptors(attributes);
213+
formatSpanName(name, { span }) {
214+
return formatFriendlySpanName(name, span.attributes);
215+
},
216+
formatAttributes({ attributes }) {
217+
return toDescriptors(attributes);
143218
},
144219
};

src/shared/tracing.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,6 @@ class ProgressTreeRenderer implements ProgressLoggerService {
587587
),
588588
].join(""),
589589
);
590-
lines.push(" ");
591590

592591
for (const rootId of this.rootOrder) {
593592
const root = this.nodes.get(rootId);
@@ -601,12 +600,10 @@ class ProgressTreeRenderer implements ProgressLoggerService {
601600
const path = this.pathOf(active)
602601
.map((node) => formatSpanDisplayName(node.name, node.span, this.config, "interactive"))
603602
.join(" > ");
604-
lines.push(" ");
605603
lines.push(
606604
`${maybeColor(this.color, ansi.dim, "Active:")} ${maybeColor(this.color, ansi.bold, path)}`,
607605
);
608606
} else if (final) {
609-
lines.push(" ");
610607
lines.push(
611608
`${maybeColor(
612609
this.color,
@@ -804,16 +801,6 @@ export const ProgressLoggerLive = Layer.effect(
804801
),
805802
);
806803

807-
export const withProgressSpan = <A, E, R>(
808-
effect: Effect.Effect<A, E, R>,
809-
name: string,
810-
attributes?: Record<string, unknown>,
811-
) =>
812-
Effect.withSpan(effect, name, {
813-
attributes,
814-
captureStackTrace: false,
815-
});
816-
817804
export const ProgressTracingLayer = Layer.effect(
818805
Tracer.Tracer,
819806
Effect.gen(function* () {

0 commit comments

Comments
 (0)