feat(server): add permission request endpoints (#33774)
This commit is contained in:
parent
bcff162c60
commit
7f40aeab21
5 changed files with 287 additions and 2 deletions
|
|
@ -814,6 +814,20 @@ const scenarios: Scenario[] = [
|
|||
object(body.location)
|
||||
array(body.data)
|
||||
}),
|
||||
http.protected
|
||||
.post("/api/session/{sessionID}/permission", "v2.session.permission.create")
|
||||
.seeded((ctx) => ctx.session({ title: "Permission create owner" }))
|
||||
.at((ctx) => ({
|
||||
path: route("/api/session/{sessionID}/permission", { sessionID: ctx.state.id }),
|
||||
headers: ctx.headers(),
|
||||
body: { action: "read", resources: [".env"] },
|
||||
}))
|
||||
.json(200, (body) => {
|
||||
object(body)
|
||||
object(body.data)
|
||||
check(typeof body.data.id === "string", "permission create should return an ID")
|
||||
check(body.data.effect === "ask", "permission create should create a pending request")
|
||||
}),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/permission", "v2.session.permission.list")
|
||||
.seeded((ctx) => ctx.session({ title: "Permission list owner" }))
|
||||
|
|
@ -822,6 +836,17 @@ const scenarios: Scenario[] = [
|
|||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(200, data(array)),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/permission/{requestID}", "v2.session.permission.get")
|
||||
.seeded((ctx) => ctx.session({ title: "Permission get owner" }))
|
||||
.at((ctx) => ({
|
||||
path: route("/api/session/{sessionID}/permission/{requestID}", {
|
||||
sessionID: ctx.state.id,
|
||||
requestID: "per_httpapi_missing",
|
||||
}),
|
||||
headers: ctx.headers(),
|
||||
}))
|
||||
.json(404, object, "status"),
|
||||
http.protected
|
||||
.get("/api/session/{sessionID}/question", "v2.session.question.list")
|
||||
.seeded((ctx) => ctx.session({ title: "Question list owner" }))
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
import { Agent } from "@opencode-ai/schema/agent"
|
||||
import { Location } from "@opencode-ai/schema/location"
|
||||
import { Permission } from "@opencode-ai/schema/permission"
|
||||
import { PermissionSaved } from "@opencode-ai/schema/permission-saved"
|
||||
import { Project } from "@opencode-ai/schema/project"
|
||||
import { Session } from "@opencode-ai/schema/session"
|
||||
|
|
@ -58,6 +59,32 @@ export const makePermissionGroup = <
|
|||
)
|
||||
// Effect applies group middleware only to endpoints already added; session endpoints use session placement below.
|
||||
.middleware(locationMiddleware)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.permission.create", "/api/session/:sessionID/permission", {
|
||||
params: { sessionID: Session.ID },
|
||||
payload: Schema.Struct({
|
||||
id: Permission.ID.pipe(Schema.optional),
|
||||
action: Permission.Request.fields.action,
|
||||
resources: Permission.Request.fields.resources,
|
||||
save: Permission.Request.fields.save,
|
||||
metadata: Permission.Request.fields.metadata,
|
||||
source: Permission.Request.fields.source,
|
||||
agent: Agent.ID.pipe(Schema.optional),
|
||||
}),
|
||||
success: Schema.Struct({
|
||||
data: Schema.Struct({ id: Permission.ID, effect: Permission.Effect }),
|
||||
}),
|
||||
error: SessionNotFoundError,
|
||||
})
|
||||
.middleware(sessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.permission.create",
|
||||
summary: "Create permission request",
|
||||
description: "Evaluate and, when approval is required, create a permission request for a session.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.permission.list", "/api/session/:sessionID/permission", {
|
||||
params: { sessionID: Session.ID },
|
||||
|
|
@ -73,6 +100,21 @@ export const makePermissionGroup = <
|
|||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.permission.get", "/api/session/:sessionID/permission/:requestID", {
|
||||
params: { sessionID: Session.ID, requestID: Permission.ID },
|
||||
success: Schema.Struct({ data: Permission.Request }),
|
||||
error: [SessionNotFoundError, PermissionNotFoundError],
|
||||
})
|
||||
.middleware(sessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.permission.get",
|
||||
summary: "Get permission request",
|
||||
description: "Retrieve a pending permission request owned by a session.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.permission.reply", "/api/session/:sessionID/permission/:requestID/reply", {
|
||||
params: { sessionID: Session.ID, requestID: Permission.ID },
|
||||
|
|
|
|||
|
|
@ -128,6 +128,7 @@ import type {
|
|||
PermissionRespondResponses,
|
||||
PermissionRuleset,
|
||||
PermissionV2Reply,
|
||||
PermissionV2Source,
|
||||
ProjectCurrentErrors,
|
||||
ProjectCurrentResponses,
|
||||
ProjectDirectoriesErrors,
|
||||
|
|
@ -341,6 +342,10 @@ import type {
|
|||
V2SessionListResponses,
|
||||
V2SessionMessagesErrors,
|
||||
V2SessionMessagesResponses,
|
||||
V2SessionPermissionCreateErrors,
|
||||
V2SessionPermissionCreateResponses,
|
||||
V2SessionPermissionGetErrors,
|
||||
V2SessionPermissionGetResponses,
|
||||
V2SessionPermissionListErrors,
|
||||
V2SessionPermissionListResponses,
|
||||
V2SessionPermissionReplyErrors,
|
||||
|
|
@ -5183,6 +5188,93 @@ export class Permission2 extends HeyApiClient {
|
|||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Create permission request
|
||||
*
|
||||
* Evaluate and, when approval is required, create a permission request for a session.
|
||||
*/
|
||||
public create<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
id?: string
|
||||
action?: string
|
||||
resources?: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source
|
||||
agent?: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ in: "body", key: "id" },
|
||||
{ in: "body", key: "action" },
|
||||
{ in: "body", key: "resources" },
|
||||
{ in: "body", key: "save" },
|
||||
{ in: "body", key: "metadata" },
|
||||
{ in: "body", key: "source" },
|
||||
{ in: "body", key: "agent" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).post<
|
||||
V2SessionPermissionCreateResponses,
|
||||
V2SessionPermissionCreateErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/session/{sessionID}/permission",
|
||||
...options,
|
||||
...params,
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
...options?.headers,
|
||||
...params.headers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get permission request
|
||||
*
|
||||
* Retrieve a pending permission request owned by a session.
|
||||
*/
|
||||
public get<ThrowOnError extends boolean = false>(
|
||||
parameters: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
},
|
||||
options?: Options<never, ThrowOnError>,
|
||||
) {
|
||||
const params = buildClientParams(
|
||||
[parameters],
|
||||
[
|
||||
{
|
||||
args: [
|
||||
{ in: "path", key: "sessionID" },
|
||||
{ in: "path", key: "requestID" },
|
||||
],
|
||||
},
|
||||
],
|
||||
)
|
||||
return (options?.client ?? this.client).get<
|
||||
V2SessionPermissionGetResponses,
|
||||
V2SessionPermissionGetErrors,
|
||||
ThrowOnError
|
||||
>({
|
||||
url: "/api/session/{sessionID}/permission/{requestID}",
|
||||
...options,
|
||||
...params,
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Reply to pending permission request
|
||||
*
|
||||
|
|
|
|||
|
|
@ -12581,6 +12581,95 @@ export type V2SessionPermissionListResponses = {
|
|||
|
||||
export type V2SessionPermissionListResponse = V2SessionPermissionListResponses[keyof V2SessionPermissionListResponses]
|
||||
|
||||
export type V2SessionPermissionCreateData = {
|
||||
body: {
|
||||
id?: string
|
||||
action: string
|
||||
resources: Array<string>
|
||||
save?: Array<string>
|
||||
metadata?: {
|
||||
[key: string]: unknown
|
||||
}
|
||||
source?: PermissionV2Source
|
||||
agent?: string
|
||||
}
|
||||
path: {
|
||||
sessionID: string
|
||||
}
|
||||
query?: never
|
||||
url: "/api/session/{sessionID}/permission"
|
||||
}
|
||||
|
||||
export type V2SessionPermissionCreateErrors = {
|
||||
/**
|
||||
* InvalidRequestError
|
||||
*/
|
||||
400: InvalidRequestError
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError
|
||||
*/
|
||||
404: SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionCreateError = V2SessionPermissionCreateErrors[keyof V2SessionPermissionCreateErrors]
|
||||
|
||||
export type V2SessionPermissionCreateResponses = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: {
|
||||
id: string
|
||||
effect: PermissionV2Effect
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionPermissionCreateResponse =
|
||||
V2SessionPermissionCreateResponses[keyof V2SessionPermissionCreateResponses]
|
||||
|
||||
export type V2SessionPermissionGetData = {
|
||||
body?: never
|
||||
path: {
|
||||
sessionID: string
|
||||
requestID: string
|
||||
}
|
||||
query?: never
|
||||
url: "/api/session/{sessionID}/permission/{requestID}"
|
||||
}
|
||||
|
||||
export type V2SessionPermissionGetErrors = {
|
||||
/**
|
||||
* InvalidRequestError
|
||||
*/
|
||||
400: InvalidRequestError
|
||||
/**
|
||||
* UnauthorizedError
|
||||
*/
|
||||
401: UnauthorizedError
|
||||
/**
|
||||
* SessionNotFoundError | PermissionNotFoundError
|
||||
*/
|
||||
404: PermissionNotFoundError | SessionNotFoundError
|
||||
}
|
||||
|
||||
export type V2SessionPermissionGetError = V2SessionPermissionGetErrors[keyof V2SessionPermissionGetErrors]
|
||||
|
||||
export type V2SessionPermissionGetResponses = {
|
||||
/**
|
||||
* Success
|
||||
*/
|
||||
200: {
|
||||
data: PermissionV2Request
|
||||
}
|
||||
}
|
||||
|
||||
export type V2SessionPermissionGetResponse = V2SessionPermissionGetResponses[keyof V2SessionPermissionGetResponses]
|
||||
|
||||
export type V2SessionPermissionReplyData = {
|
||||
body: {
|
||||
reply: PermissionV2Reply
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ import { PermissionSaved } from "@opencode-ai/core/permission/saved"
|
|||
import { Effect } from "effect"
|
||||
import { HttpApiBuilder, HttpApiSchema } from "effect/unstable/httpapi"
|
||||
import { Api } from "../api"
|
||||
import { PermissionNotFoundError } from "@opencode-ai/protocol/errors"
|
||||
import { PermissionNotFoundError, SessionNotFoundError } from "@opencode-ai/protocol/errors"
|
||||
import { response } from "../location"
|
||||
|
||||
function missingRequest(id: PermissionV2.ID) {
|
||||
|
|
@ -20,6 +20,35 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
|
|||
return yield* response((yield* PermissionV2.Service).list())
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.create",
|
||||
Effect.fn(function* (ctx) {
|
||||
const permission = yield* PermissionV2.Service
|
||||
return {
|
||||
data: yield* permission
|
||||
.ask({
|
||||
id: ctx.payload.id,
|
||||
sessionID: ctx.params.sessionID,
|
||||
action: ctx.payload.action,
|
||||
resources: ctx.payload.resources,
|
||||
save: ctx.payload.save,
|
||||
metadata: ctx.payload.metadata,
|
||||
source: ctx.payload.source,
|
||||
agent: ctx.payload.agent,
|
||||
})
|
||||
.pipe(
|
||||
Effect.catchTag(
|
||||
"Session.NotFoundError",
|
||||
(error) =>
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.list",
|
||||
Effect.fn(function* (ctx) {
|
||||
|
|
@ -27,6 +56,14 @@ export const PermissionHandler = HttpApiBuilder.group(Api, "server.permission",
|
|||
return { data: yield* permission.forSession(ctx.params.sessionID) }
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.get",
|
||||
Effect.fn(function* (ctx) {
|
||||
const request = yield* (yield* PermissionV2.Service).get(ctx.params.requestID)
|
||||
if (!request || request.sessionID !== ctx.params.sessionID) return yield* missingRequest(ctx.params.requestID)
|
||||
return { data: request }
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.permission.reply",
|
||||
Effect.fn(function* (ctx) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue