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) => { async (state) => {
for (const pending of Object.values(state.pending)) { for (const pending of Object.values(state.pending)) {
for (const item of Object.values(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) }).then((x) => x.status)
) { ) {
case "deny": case "deny":
throw new RejectedError(info.sessionID, info.id, info.callID) throw new RejectedError(info.sessionID, info.id, info.callID, info.metadata)
case "allow": case "allow":
return return
} }
@@ -131,7 +131,7 @@ export namespace Permission {
if (!match) return if (!match) return
delete pending[input.sessionID][input.permissionID] delete pending[input.sessionID][input.permissionID]
if (input.response === "reject") { 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 return
} }
match.resolve() match.resolve()
@@ -156,6 +156,7 @@ export namespace Permission {
public readonly sessionID: string, public readonly sessionID: string,
public readonly permissionID: string, public readonly permissionID: string,
public readonly toolCallID?: 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.`) 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", status: "error",
input: value.input, input: value.input,
error: (value.error as any).toString(), error: (value.error as any).toString(),
metadata: value.error instanceof Permission.RejectedError ? value.error.metadata : undefined,
time: { time: {
start: match.state.time.start, start: match.state.time.start,
end: Date.now(), end: Date.now(),

View File

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

View File

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

View File

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

View File

@@ -554,6 +554,17 @@ func renderToolDetails(
title := renderToolTitle(toolCall, width) title := renderToolTitle(toolCall, width)
title = style.Render(title) title = style.Render(title)
content := title + "\n" + body 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 != "" { if permissionContent != "" {
permissionContent = styles.NewStyle(). permissionContent = styles.NewStyle().
Background(backgroundColor). Background(backgroundColor).
@@ -652,11 +663,17 @@ func renderToolDetails(
} }
if error != "" { if error != "" {
body = styles.NewStyle(). errorContent := styles.NewStyle().
Width(width - 6). Width(width - 6).
Foreground(t.Error()). Foreground(t.Error()).
Background(backgroundColor). Background(backgroundColor).
Render(error) Render(error)
if body == "" {
body = errorContent
} else {
body += "\n\n" + errorContent
}
} }
if body == "" && error == "" && result != nil { if body == "" && error == "" && result != nil {