Feat: Render tool metadata after permission rejection. (#1949)

Signed-off-by: jmug <u.g.a.mariano@gmail.com>
This commit is contained in:
Mariano Uvalle
2025-08-15 04:16:40 -07:00
committed by GitHub
parent 8355ee2061
commit 0befc5d602
6 changed files with 34 additions and 9 deletions

View File

@@ -62,7 +62,7 @@ export namespace Permission {
async (state) => {
for (const pending of Object.values(state.pending)) {
for (const item of Object.values(pending)) {
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID))
item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID, item.info.metadata))
}
}
},
@@ -105,7 +105,7 @@ export namespace Permission {
}).then((x) => x.status)
) {
case "deny":
throw new RejectedError(info.sessionID, info.id, info.callID)
throw new RejectedError(info.sessionID, info.id, info.callID, info.metadata)
case "allow":
return
}
@@ -131,7 +131,7 @@ export namespace Permission {
if (!match) return
delete pending[input.sessionID][input.permissionID]
if (input.response === "reject") {
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID))
match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID, match.info.metadata))
return
}
match.resolve()
@@ -156,6 +156,7 @@ export namespace Permission {
public readonly sessionID: string,
public readonly permissionID: string,
public readonly toolCallID?: string,
public readonly metadata?: Record<string, any>,
) {
super(`The user rejected permission to use this specific tool call. You may try again with different parameters.`)
}

View File

@@ -1268,6 +1268,7 @@ export namespace Session {
status: "error",
input: value.input,
error: (value.error as any).toString(),
metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined,
time: {
start: match.state.time.start,
end: Date.now(),

View File

@@ -64,6 +64,7 @@ export namespace MessageV2 {
status: z.literal("error"),
input: z.record(z.any()),
error: z.string(),
metadata: z.record(z.any()).optional(),
time: z.object({
start: z.number(),
end: z.number(),

View File

@@ -2025,6 +2025,7 @@ func (r toolStateCompletedTimeJSON) RawJSON() string {
type ToolStateError struct {
Error string `json:"error,required"`
Input map[string]interface{} `json:"input,required"`
Metadata map[string]interface{} `json:"metadata"`
Status ToolStateErrorStatus `json:"status,required"`
Time ToolStateErrorTime `json:"time,required"`
JSON toolStateErrorJSON `json:"-"`
@@ -2034,6 +2035,7 @@ type ToolStateError struct {
type toolStateErrorJSON struct {
Error apijson.Field
Input apijson.Field
Metadata apijson.Field
Status apijson.Field
Time apijson.Field
raw string

View File

@@ -347,6 +347,9 @@ export type ToolStateError = {
input: {
[key: string]: unknown
}
metadata: {
[key: string]: unknown
}
error: string
time: {
start: number

View File

@@ -554,6 +554,17 @@ func renderToolDetails(
title := renderToolTitle(toolCall, width)
title = style.Render(title)
content := title + "\n" + body
if toolCall.State.Status == opencode.ToolPartStateStatusError {
errorStyle := styles.NewStyle().
Background(backgroundColor).
Foreground(t.Error()).
Padding(1, 2).
Width(width - 4)
errorContent := errorStyle.Render(toolCall.State.Error)
content += "\n" + errorContent
}
if permissionContent != "" {
permissionContent = styles.NewStyle().
Background(backgroundColor).
@@ -652,11 +663,17 @@ func renderToolDetails(
}
if error != "" {
body = styles.NewStyle().
errorContent := styles.NewStyle().
Width(width - 6).
Foreground(t.Error()).
Background(backgroundColor).
Render(error)
if body == "" {
body = errorContent
} else {
body += "\n\n" + errorContent
}
}
if body == "" && error == "" && result != nil {