Update UI permissions for SDK 1.0.166

Handle permission.asked events and requestID replies while keeping legacy compatibility.
This commit is contained in:
Shantur Rathore
2026-01-04 20:50:25 +00:00
parent c2df32ec8b
commit fcb5998474
11 changed files with 226 additions and 66 deletions

View File

@@ -1,6 +1,8 @@
import { createSignal } from "solid-js"
import type { Instance, LogEntry } from "../types/instance"
import type { LspStatus, Permission } from "@opencode-ai/sdk"
import type { LspStatus } from "@opencode-ai/sdk"
import type { PermissionReply, PermissionRequestLike } from "../types/permission"
import { getPermissionCreatedAt, getPermissionSessionId } from "../types/permission"
import { sdkManager } from "../lib/sdk-manager"
import { sseManager } from "../lib/sse-manager"
import { serverApi } from "../lib/api-client"
@@ -31,7 +33,7 @@ const [instanceLogs, setInstanceLogs] = createSignal<Map<string, LogEntry[]>>(ne
const [logStreamingState, setLogStreamingState] = createSignal<Map<string, boolean>>(new Map())
// Permission queue management per instance
const [permissionQueues, setPermissionQueues] = createSignal<Map<string, Permission[]>>(new Map())
const [permissionQueues, setPermissionQueues] = createSignal<Map<string, PermissionRequestLike[]>>(new Map())
const [activePermissionId, setActivePermissionId] = createSignal<Map<string, string | null>>(new Map())
const permissionSessionCounts = new Map<string, Map<string, number>>()
@@ -382,7 +384,7 @@ function clearLogs(id: string) {
}
// Permission management functions
function getPermissionQueue(instanceId: string): Permission[] {
function getPermissionQueue(instanceId: string): PermissionRequestLike[] {
const queue = permissionQueues().get(instanceId)
if (!queue) {
return []
@@ -429,7 +431,7 @@ function clearSessionPendingCounts(instanceId: string): void {
permissionSessionCounts.delete(instanceId)
}
function addPermissionToQueue(instanceId: string, permission: Permission): void {
function addPermissionToQueue(instanceId: string, permission: PermissionRequestLike): void {
let inserted = false
setPermissionQueues((prev) => {
@@ -440,7 +442,7 @@ function addPermissionToQueue(instanceId: string, permission: Permission): void
return next
}
const updatedQueue = [...queue, permission].sort((a, b) => a.time.created - b.time.created)
const updatedQueue = [...queue, permission].sort((a, b) => getPermissionCreatedAt(a) - getPermissionCreatedAt(b))
next.set(instanceId, updatedQueue)
inserted = true
return next
@@ -459,17 +461,19 @@ function addPermissionToQueue(instanceId: string, permission: Permission): void
})
const sessionId = getPermissionSessionId(permission)
incrementSessionPendingCount(instanceId, sessionId)
setSessionPendingPermission(instanceId, sessionId, true)
if (sessionId) {
incrementSessionPendingCount(instanceId, sessionId)
setSessionPendingPermission(instanceId, sessionId, true)
}
}
function removePermissionFromQueue(instanceId: string, permissionId: string): void {
let removedPermission: Permission | null = null
let removedPermission: PermissionRequestLike | null = null
setPermissionQueues((prev) => {
const next = new Map(prev)
const queue = next.get(instanceId) ?? []
const filtered: Permission[] = []
const filtered: PermissionRequestLike[] = []
for (const item of queue) {
if (item.id === permissionId) {
@@ -493,7 +497,7 @@ function removePermissionFromQueue(instanceId: string, permissionId: string): vo
const next = new Map(prev)
const activeId = next.get(instanceId)
if (activeId === permissionId) {
const nextPermission = updatedQueue.length > 0 ? (updatedQueue[0] as Permission) : null
const nextPermission = updatedQueue.length > 0 ? (updatedQueue[0] as PermissionRequestLike) : null
next.set(instanceId, nextPermission?.id ?? null)
}
return next
@@ -502,8 +506,10 @@ function removePermissionFromQueue(instanceId: string, permissionId: string): vo
const removed = removedPermission
if (removed) {
const removedSessionId = getPermissionSessionId(removed)
const remaining = decrementSessionPendingCount(instanceId, removedSessionId)
setSessionPendingPermission(instanceId, removedSessionId, remaining > 0)
if (removedSessionId) {
const remaining = decrementSessionPendingCount(instanceId, removedSessionId)
setSessionPendingPermission(instanceId, removedSessionId, remaining > 0)
}
}
}
@@ -521,29 +527,55 @@ function clearPermissionQueue(instanceId: string): void {
clearSessionPendingCounts(instanceId)
}
function getPermissionSessionId(permission: Permission): string {
return (permission as any).sessionID
}
async function sendPermissionResponse(
instanceId: string,
sessionId: string,
permissionId: string,
response: "once" | "always" | "reject"
requestId: string,
reply: PermissionReply
): Promise<void> {
const instance = instances().get(instanceId)
if (!instance?.client) {
throw new Error("Instance not ready")
}
const client: any = instance.client
try {
await instance.client.postSessionIdPermissionsPermissionId({
path: { id: sessionId, permissionID: permissionId },
body: { response },
})
// New API (preferred): POST /permission/:requestID/reply
if (typeof client.postPermissionRequestIdReply === "function") {
await client.postPermissionRequestIdReply({
path: { requestID: requestId },
body: { reply },
})
} else if (typeof client.postPermissionRequestIDReply === "function") {
await client.postPermissionRequestIDReply({
path: { requestID: requestId },
body: { reply },
})
} else if (typeof client.postPermissionRequestIdReply2 === "function") {
await client.postPermissionRequestIdReply2({
path: { requestID: requestId },
body: { reply },
})
} else if (client.permission && typeof client.permission.reply === "function") {
await client.permission.reply({
path: { requestID: requestId },
body: { reply },
})
} else if (typeof client.postSessionIdPermissionsPermissionId === "function") {
// Legacy API fallback: POST /session/:sessionID/permissions/:permissionID
await client.postSessionIdPermissionsPermissionId({
path: { id: sessionId, permissionID: requestId },
body: { response: reply },
})
} else {
throw new Error("Unsupported permissions API in client")
}
// Remove from queue after successful response
removePermissionFromQueue(instanceId, permissionId)
removePermissionFromQueue(instanceId, requestId)
} catch (error) {
log.error("Failed to send permission response", error)
throw error

View File

@@ -1,4 +1,5 @@
import type { Permission } from "@opencode-ai/sdk"
import type { PermissionRequestLike } from "../../types/permission"
import { getPermissionCallId, getPermissionMessageId } from "../../types/permission"
import type { Message, MessageInfo, ClientPart } from "../../types/message"
import type { Session } from "../../types/session"
import { messageStoreBus } from "./bus"
@@ -107,11 +108,11 @@ export function replaceMessageIdV2(instanceId: string, oldId: string, newId: str
store.replaceMessageId({ oldId, newId })
}
function extractPermissionMessageId(permission: Permission): string | undefined {
return (permission as any).messageID || (permission as any).messageId
function extractPermissionMessageId(permission: PermissionRequestLike): string | undefined {
return getPermissionMessageId(permission)
}
function extractPermissionPartId(permission: Permission): string | undefined {
function extractPermissionPartId(permission: PermissionRequestLike): string | undefined {
const metadata = (permission as any).metadata || {}
return (
(permission as any).partID ||
@@ -122,17 +123,8 @@ function extractPermissionPartId(permission: Permission): string | undefined {
)
}
function extractPermissionCallId(permission: Permission): string | undefined {
const metadata = (permission as any).metadata || {}
return (
(permission as any).callID ||
(permission as any).callId ||
(permission as any).toolCallID ||
(permission as any).toolCallId ||
metadata.callID ||
metadata.callId ||
undefined
)
function extractPermissionCallId(permission: PermissionRequestLike): string | undefined {
return getPermissionCallId(permission)
}
function resolvePartIdFromCallId(store: ReturnType<typeof messageStoreBus.getOrCreate>, messageId?: string, callId?: string): string | undefined {
@@ -155,7 +147,7 @@ function resolvePartIdFromCallId(store: ReturnType<typeof messageStoreBus.getOrC
return undefined
}
export function upsertPermissionV2(instanceId: string, permission: Permission): void {
export function upsertPermissionV2(instanceId: string, permission: PermissionRequestLike): void {
if (!permission) return
const store = messageStoreBus.getOrCreate(instanceId)
const messageId = extractPermissionMessageId(permission)

View File

@@ -521,6 +521,8 @@ export function createInstanceMessageStore(instanceId: string, hooks?: MessageSt
for (const [key, entry] of Object.entries(draft)) {
if (!entry || entry.partId) continue
const permissionCallId =
(entry.permission as any).tool?.callID ??
(entry.permission as any).tool?.callId ??
(entry.permission as any).callID ??
(entry.permission as any).callId ??
(entry.permission as any).toolCallID ??

View File

@@ -1,5 +1,5 @@
import type { ClientPart } from "../../types/message"
import type { Permission } from "@opencode-ai/sdk"
import type { PermissionRequestLike } from "../../types/permission"
export type MessageStatus = "sending" | "sent" | "streaming" | "complete" | "error"
export type MessageRole = "user" | "assistant"
@@ -47,7 +47,7 @@ export interface PendingPartEntry {
}
export interface PermissionEntry {
permission: Permission
permission: PermissionRequestLike
messageId?: string
partId?: string
enqueuedAt: number

View File

@@ -6,8 +6,6 @@ import type {
MessageUpdateEvent,
} from "../types/message"
import type {
EventPermissionReplied,
EventPermissionUpdated,
EventSessionCompacted,
EventSessionError,
EventSessionIdle,
@@ -17,6 +15,8 @@ import type {
import type { MessageStatus } from "./message-v2/types"
import { getLogger } from "../lib/logger"
import { getPermissionId, getPermissionKind, getRequestIdFromPermissionReply } from "../types/permission"
import type { PermissionReplyEventPropertiesLike, PermissionRequestLike } from "../types/permission"
import { showToastNotification, ToastVariant } from "../lib/notifications"
import { instances, addPermissionToQueue, removePermissionFromQueue } from "./instances"
import { showAlertDialog } from "./alerts"
@@ -442,22 +442,23 @@ function handleTuiToast(_instanceId: string, event: TuiToastEvent): void {
})
}
function handlePermissionUpdated(instanceId: string, event: EventPermissionUpdated): void {
const permission = event.properties
function handlePermissionUpdated(instanceId: string, event: { type: string; properties?: PermissionRequestLike } | any): void {
const permission = event?.properties as PermissionRequestLike | undefined
if (!permission) return
log.info(`[SSE] Permission updated: ${permission.id} (${permission.type})`)
log.info(`[SSE] Permission request: ${getPermissionId(permission)} (${getPermissionKind(permission)})`)
addPermissionToQueue(instanceId, permission)
upsertPermissionV2(instanceId, permission)
}
function handlePermissionReplied(instanceId: string, event: EventPermissionReplied): void {
const { permissionID } = event.properties
if (!permissionID) return
function handlePermissionReplied(instanceId: string, event: { type: string; properties?: PermissionReplyEventPropertiesLike } | any): void {
const properties = event?.properties as PermissionReplyEventPropertiesLike | undefined
const requestId = getRequestIdFromPermissionReply(properties)
if (!requestId) return
log.info(`[SSE] Permission replied: ${permissionID}`)
removePermissionFromQueue(instanceId, permissionID)
removePermissionV2(instanceId, permissionID)
log.info(`[SSE] Permission replied: ${requestId}`)
removePermissionFromQueue(instanceId, requestId)
removePermissionV2(instanceId, requestId)
}
export {