diff --git a/packages/core/migration/20260601202201_amazing_prowler/snapshot.json b/packages/core/migration/20260601202201_amazing_prowler/snapshot.json index 3c5e0ae6c..b506b5009 100644 --- a/packages/core/migration/20260601202201_amazing_prowler/snapshot.json +++ b/packages/core/migration/20260601202201_amazing_prowler/snapshot.json @@ -2,9 +2,7 @@ "version": "7", "dialect": "sqlite", "id": "226375f1-a19f-4c7b-8aa2-ccc5513d3b0d", - "prevIds": [ - "bf93c73b-5a48-4d63-9909-3c36a79b9788" - ], + "prevIds": ["bf93c73b-5a48-4d63-9909-3c36a79b9788"], "ddl": [ { "name": "workspace", @@ -1143,13 +1141,9 @@ "table": "session_share" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1158,13 +1152,9 @@ "table": "workspace" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -1173,13 +1163,9 @@ "table": "account_state" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "tableTo": "event_sequence", - "columnsTo": [ - "aggregate_id" - ], + "columnsTo": ["aggregate_id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1188,13 +1174,9 @@ "table": "event" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1203,13 +1185,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1218,13 +1196,9 @@ "table": "part" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1233,13 +1207,9 @@ "table": "session_message" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1248,13 +1218,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1263,13 +1229,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1278,128 +1240,98 @@ "table": "session_share" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "name" - ], + "columns": ["name"], "nameExplicit": false, "name": "data_migration_pk", "table": "data_migration", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_state_pk", "table": "account_state", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_pk", "table": "account", "entityType": "pks" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "nameExplicit": false, "name": "event_sequence_pk", "table": "event_sequence", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "event_pk", "table": "event", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "message_pk", "table": "message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "part_pk", "table": "part", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_message_pk", "table": "session_message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_pk", "table": "session", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "session_share_pk", "table": "session_share", @@ -1563,4 +1495,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/core/migration/20260602002951_lowly_union_jack/snapshot.json b/packages/core/migration/20260602002951_lowly_union_jack/snapshot.json index 77777d4aa..ca0be6da3 100644 --- a/packages/core/migration/20260602002951_lowly_union_jack/snapshot.json +++ b/packages/core/migration/20260602002951_lowly_union_jack/snapshot.json @@ -2,9 +2,7 @@ "version": "7", "dialect": "sqlite", "id": "80d6efb8-93fd-4ce5-b320-45a05aaebdd7", - "prevIds": [ - "226375f1-a19f-4c7b-8aa2-ccc5513d3b0d" - ], + "prevIds": ["226375f1-a19f-4c7b-8aa2-ccc5513d3b0d"], "ddl": [ { "name": "workspace", @@ -1207,13 +1205,9 @@ "table": "session_share" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1222,13 +1216,9 @@ "table": "workspace" }, { - "columns": [ - "active_account_id" - ], + "columns": ["active_account_id"], "tableTo": "account", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "SET NULL", "nameExplicit": false, @@ -1237,13 +1227,9 @@ "table": "account_state" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "tableTo": "event_sequence", - "columnsTo": [ - "aggregate_id" - ], + "columnsTo": ["aggregate_id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1252,13 +1238,9 @@ "table": "event" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1267,13 +1249,9 @@ "table": "permission" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1282,13 +1260,9 @@ "table": "message" }, { - "columns": [ - "message_id" - ], + "columns": ["message_id"], "tableTo": "message", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1297,13 +1271,9 @@ "table": "part" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1312,13 +1282,9 @@ "table": "session_message" }, { - "columns": [ - "project_id" - ], + "columns": ["project_id"], "tableTo": "project", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1327,13 +1293,9 @@ "table": "session" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1342,13 +1304,9 @@ "table": "todo" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "tableTo": "session", - "columnsTo": [ - "id" - ], + "columnsTo": ["id"], "onUpdate": "NO ACTION", "onDelete": "CASCADE", "nameExplicit": false, @@ -1357,137 +1315,105 @@ "table": "session_share" }, { - "columns": [ - "email", - "url" - ], + "columns": ["email", "url"], "nameExplicit": false, "name": "control_account_pk", "entityType": "pks", "table": "control_account" }, { - "columns": [ - "session_id", - "position" - ], + "columns": ["session_id", "position"], "nameExplicit": false, "name": "todo_pk", "entityType": "pks", "table": "todo" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "workspace_pk", "table": "workspace", "entityType": "pks" }, { - "columns": [ - "name" - ], + "columns": ["name"], "nameExplicit": false, "name": "data_migration_pk", "table": "data_migration", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_state_pk", "table": "account_state", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "account_pk", "table": "account", "entityType": "pks" }, { - "columns": [ - "aggregate_id" - ], + "columns": ["aggregate_id"], "nameExplicit": false, "name": "event_sequence_pk", "table": "event_sequence", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "event_pk", "table": "event", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "permission_pk", "table": "permission", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "project_pk", "table": "project", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "message_pk", "table": "message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "part_pk", "table": "part", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_message_pk", "table": "session_message", "entityType": "pks" }, { - "columns": [ - "id" - ], + "columns": ["id"], "nameExplicit": false, "name": "session_pk", "table": "session", "entityType": "pks" }, { - "columns": [ - "session_id" - ], + "columns": ["session_id"], "nameExplicit": false, "name": "session_share_pk", "table": "session_share", @@ -1673,4 +1599,4 @@ } ], "renames": [] -} \ No newline at end of file +} diff --git a/packages/core/src/database/migration/20260602002951_lowly_union_jack.ts b/packages/core/src/database/migration/20260602002951_lowly_union_jack.ts index 45cd8aafa..6c75b52ac 100644 --- a/packages/core/src/database/migration/20260602002951_lowly_union_jack.ts +++ b/packages/core/src/database/migration/20260602002951_lowly_union_jack.ts @@ -16,7 +16,9 @@ export default { CONSTRAINT \`fk_permission_project_id_project_id_fk\` FOREIGN KEY (\`project_id\`) REFERENCES \`project\`(\`id\`) ON DELETE CASCADE ); `) - yield* tx.run(`CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`) + yield* tx.run( + `CREATE UNIQUE INDEX \`permission_project_action_resource_idx\` ON \`permission\` (\`project_id\`,\`action\`,\`resource\`);`, + ) }) }, } satisfies DatabaseMigration.Migration diff --git a/packages/core/src/permission.ts b/packages/core/src/permission.ts index d78f45c56..a7c324720 100644 --- a/packages/core/src/permission.ts +++ b/packages/core/src/permission.ts @@ -254,7 +254,11 @@ export const layer = Layer.effect( } if (input.reply === "always" && existing.request.save?.length) { - yield* saved.add({ projectID: location.project.id, action: existing.request.action, resources: existing.request.save }) + yield* saved.add({ + projectID: location.project.id, + action: existing.request.action, + resources: existing.request.save, + }) } yield* Deferred.succeed(existing.deferred, undefined) if (input.reply !== "always" || !existing.request.save?.length) return @@ -269,7 +273,9 @@ export const layer = Layer.effect( if (denied(input, rules)) continue const effective = [...rules, ...rememberedRules] if ( - !item.request.resources.every((resource) => evaluate(item.request.action, resource, effective).effect === "allow") + !item.request.resources.every( + (resource) => evaluate(item.request.action, resource, effective).effect === "allow", + ) ) continue pending.delete(id) diff --git a/packages/core/src/permission/saved.ts b/packages/core/src/permission/saved.ts index 110f3d23b..4c57ef2aa 100644 --- a/packages/core/src/permission/saved.ts +++ b/packages/core/src/permission/saved.ts @@ -54,14 +54,23 @@ export const layer = Layer.effect( .where(input?.projectID ? eq(PermissionTable.project_id, input.projectID) : undefined) .all() .pipe(Effect.orDie) - return rows.map((row): Info => ({ id: row.id, projectID: row.project_id, action: row.action, resource: row.resource })) + return rows.map( + (row): Info => ({ id: row.id, projectID: row.project_id, action: row.action, resource: row.resource }), + ) }) const add = Effect.fn("PermissionSaved.add")(function* (input: AddInput) { if (!input.resources.length) return yield* db .insert(PermissionTable) - .values(input.resources.map((resource) => ({ id: ID.create(), project_id: input.projectID, action: input.action, resource }))) + .values( + input.resources.map((resource) => ({ + id: ID.create(), + project_id: input.projectID, + action: input.action, + resource, + })), + ) .onConflictDoNothing() .run() .pipe(Effect.orDie) diff --git a/packages/core/src/plugin/agent.ts b/packages/core/src/plugin/agent.ts index 9e568dac7..694bd3d79 100644 --- a/packages/core/src/plugin/agent.ts +++ b/packages/core/src/plugin/agent.ts @@ -158,9 +158,7 @@ export const Plugin = PluginV2.define({ item.description = "General-purpose agent for researching complex questions and executing multi-step tasks. Use this agent to execute multiple units of work in parallel." item.mode = "subagent" - item.permissions.push( - ...PermissionV2.merge(defaults, [{ action: "todowrite", resource: "*", effect: "deny" }]), - ) + item.permissions.push(...PermissionV2.merge(defaults, [{ action: "todowrite", resource: "*", effect: "deny" }])) }) editor.update(AgentV2.ID.make("explore"), (item) => { diff --git a/packages/core/test/permission.test.ts b/packages/core/test/permission.test.ts index 6bb9211f9..a3abc4e72 100644 --- a/packages/core/test/permission.test.ts +++ b/packages/core/test/permission.test.ts @@ -160,14 +160,12 @@ describe("PermissionV2", () => { yield* Fiber.join(fiber) const { db } = yield* Database.Service - expect(yield* db.select().from(PermissionTable).where(eq(PermissionTable.project_id, Project.ID.global)).all()).toMatchObject([ - { action: "read", resource: "src/*" }, - ]) + expect( + yield* db.select().from(PermissionTable).where(eq(PermissionTable.project_id, Project.ID.global)).all(), + ).toMatchObject([{ action: "read", resource: "src/*" }]) const saved = yield* PermissionSaved.Service const id = (yield* saved.list())[0]!.id - expect(yield* saved.list()).toEqual([ - { id, projectID: Project.ID.global, action: "read", resource: "src/*" }, - ]) + expect(yield* saved.list()).toEqual([{ id, projectID: Project.ID.global, action: "read", resource: "src/*" }]) yield* service.assert(assertion({ id: PermissionV2.ID.create("per_next"), resources: ["src/next.ts"] })) yield* saved.remove(id) expect(yield* saved.list()).toEqual([]) diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts index f14e6efd2..375a9b43e 100644 --- a/packages/opencode/src/permission/index.ts +++ b/packages/opencode/src/permission/index.ts @@ -38,7 +38,11 @@ interface State { approved: PermissionLegacy.Rule[] } -export function evaluate(permission: string, pattern: string, ...rulesets: PermissionLegacy.Ruleset[]): PermissionLegacy.Rule { +export function evaluate( + permission: string, + pattern: string, + ...rulesets: PermissionLegacy.Ruleset[] +): PermissionLegacy.Rule { return ( rulesets .flat() @@ -134,7 +138,9 @@ export const layer = Layer.effect( if (input.reply === "reject") { yield* Deferred.fail( existing.deferred, - input.message ? new PermissionLegacy.CorrectedError({ feedback: input.message }) : new PermissionLegacy.RejectedError(), + input.message + ? new PermissionLegacy.CorrectedError({ feedback: input.message }) + : new PermissionLegacy.RejectedError(), ) for (const [id, item] of pending.entries()) { diff --git a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/permission.ts b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/permission.ts index 0dd518b46..c1f78089a 100644 --- a/packages/opencode/src/server/routes/instance/httpapi/groups/v2/permission.ts +++ b/packages/opencode/src/server/routes/instance/httpapi/groups/v2/permission.ts @@ -58,7 +58,9 @@ export const SessionPermissionGroup = HttpApiGroup.make("v2.session.permission") }), ), ) - .annotateMerge(OpenApi.annotations({ title: "v2 session permissions", description: "Experimental v2 session permission routes." })) + .annotateMerge( + OpenApi.annotations({ title: "v2 session permissions", description: "Experimental v2 session permission routes." }), + ) .middleware(V2Authorization) export const PermissionSavedGroup = HttpApiGroup.make("v2.permission.saved") @@ -86,5 +88,7 @@ export const PermissionSavedGroup = HttpApiGroup.make("v2.permission.saved") }), ), ) - .annotateMerge(OpenApi.annotations({ title: "v2 saved permissions", description: "Experimental v2 saved permission routes." })) + .annotateMerge( + OpenApi.annotations({ title: "v2 saved permissions", description: "Experimental v2 saved permission routes." }), + ) .middleware(V2Authorization) diff --git a/packages/opencode/test/permission/next.test.ts b/packages/opencode/test/permission/next.test.ts index f26dba2d7..e505cf000 100644 --- a/packages/opencode/test/permission/next.test.ts +++ b/packages/opencode/test/permission/next.test.ts @@ -917,7 +917,11 @@ it.instance( () => Effect.gen(function* () { const events = yield* EventV2Bridge.Service - const seen = yield* Deferred.make<{ sessionID: SessionID; requestID: PermissionLegacy.ID; reply: PermissionLegacy.Reply }>() + const seen = yield* Deferred.make<{ + sessionID: SessionID + requestID: PermissionLegacy.ID + reply: PermissionLegacy.Reply + }>() const fiber = yield* ask({ id: PermissionLegacy.ID.make("per_test7"), @@ -935,7 +939,9 @@ it.instance( if (event.type === Permission.Event.Replied.type) Deferred.doneUnsafe( seen, - Effect.succeed(event.data as { sessionID: SessionID; requestID: PermissionLegacy.ID; reply: PermissionLegacy.Reply }), + Effect.succeed( + event.data as { sessionID: SessionID; requestID: PermissionLegacy.ID; reply: PermissionLegacy.Reply }, + ), ) return Effect.void }) diff --git a/packages/sdk/openapi.json b/packages/sdk/openapi.json index c5990ae47..dc6e39e4b 100644 --- a/packages/sdk/openapi.json +++ b/packages/sdk/openapi.json @@ -9016,6 +9016,354 @@ ] } }, + "/api/permission/request": { + "get": { + "tags": ["v2 permissions"], + "operationId": "v2.permission.request.list", + "parameters": [ + { + "name": "location", + "in": "query", + "schema": { + "type": "object", + "properties": { + "directory": { + "type": "string" + }, + "workspace": { + "type": "string" + } + }, + "additionalProperties": false + }, + "required": false, + "style": "deepObject", + "explode": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionV2Request" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Retrieve pending permission requests for a location.", + "summary": "List pending permission requests", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.permission.request.list({\n ...\n})" + } + ] + } + }, + "/api/session/{sessionID}/permission/request": { + "get": { + "tags": ["v2 session permissions"], + "operationId": "v2.session.permission.list", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^ses" + }, + "required": true + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionV2Request" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SessionNotFoundError" + } + } + } + } + }, + "description": "Retrieve pending permission requests owned by a session.", + "summary": "List session permission requests", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.permission.list({\n ...\n})" + } + ] + } + }, + "/api/session/{sessionID}/permission/request/{requestID}/reply": { + "post": { + "tags": ["v2 session permissions"], + "operationId": "v2.session.permission.reply", + "parameters": [ + { + "name": "sessionID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^ses" + }, + "required": true + }, + { + "name": "requestID", + "in": "path", + "schema": { + "type": "string", + "pattern": "^per" + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + }, + "404": { + "description": "SessionNotFoundError | PermissionNotFoundError", + "content": { + "application/json": { + "schema": { + "anyOf": [ + { + "$ref": "#/components/schemas/SessionNotFoundError" + }, + { + "$ref": "#/components/schemas/PermissionNotFoundError" + } + ] + } + } + } + } + }, + "description": "Respond to a pending permission request owned by a session.", + "summary": "Reply to pending permission request", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "reply": { + "$ref": "#/components/schemas/PermissionV2Reply" + }, + "message": { + "type": "string" + } + }, + "required": ["reply"], + "additionalProperties": false + } + } + } + }, + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.session.permission.reply({\n ...\n})" + } + ] + } + }, + "/api/permission/saved": { + "get": { + "tags": ["v2 saved permissions"], + "operationId": "v2.permission.saved.list", + "parameters": [ + { + "name": "projectID", + "in": "query", + "schema": { + "type": "string" + }, + "required": false + } + ], + "security": [], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionSavedInfo" + } + } + } + } + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Retrieve saved permissions, optionally filtered by project.", + "summary": "List saved permissions", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.permission.saved.list({\n ...\n})" + } + ] + } + }, + "/api/permission/saved/{id}": { + "delete": { + "tags": ["v2 saved permissions"], + "operationId": "v2.permission.saved.remove", + "parameters": [ + { + "name": "id", + "in": "path", + "schema": { + "type": "string" + }, + "required": true + } + ], + "security": [], + "responses": { + "204": { + "description": "" + }, + "400": { + "description": "InvalidRequestError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/InvalidRequestError" + } + } + } + }, + "401": { + "description": "UnauthorizedError", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UnauthorizedError" + } + } + } + } + }, + "description": "Remove a saved permission by ID.", + "summary": "Remove saved permission", + "x-codeSamples": [ + { + "lang": "js", + "source": "import { createOpencodeClient } from \"@opencode-ai/sdk\n\nconst client = createOpencodeClient()\nawait client.v2.permission.saved.remove({\n ...\n})" + } + ] + } + }, "/tui/append-prompt": { "post": { "tags": ["tui"], @@ -10605,18 +10953,18 @@ { "$ref": "#/components/schemas/EventMessagePartDelta" }, - { - "$ref": "#/components/schemas/EventPermissionAsked" - }, - { - "$ref": "#/components/schemas/EventPermissionReplied" - }, { "$ref": "#/components/schemas/EventSessionDiff" }, { "$ref": "#/components/schemas/EventSessionError" }, + { + "$ref": "#/components/schemas/EventPermissionAsked" + }, + { + "$ref": "#/components/schemas/EventPermissionReplied" + }, { "$ref": "#/components/schemas/EventQuestionAsked" }, @@ -10707,6 +11055,12 @@ { "$ref": "#/components/schemas/EventGlobalDisposed" }, + { + "$ref": "#/components/schemas/EventPermissionV2Asked" + }, + { + "$ref": "#/components/schemas/EventPermissionV2Replied" + }, { "$ref": "#/components/schemas/EventAccountAdded" }, @@ -10916,6 +11270,32 @@ "required": ["additions", "deletions"], "additionalProperties": false }, + "PermissionAction": { + "type": "string", + "enum": ["allow", "deny", "ask"] + }, + "PermissionRule": { + "type": "object", + "properties": { + "permission": { + "type": "string" + }, + "pattern": { + "type": "string" + }, + "action": { + "$ref": "#/components/schemas/PermissionAction" + } + }, + "required": ["permission", "pattern", "action"], + "additionalProperties": false + }, + "PermissionRuleset": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PermissionRule" + } + }, "Session": { "type": "object", "properties": { @@ -13877,100 +14257,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["permission.asked"] - }, - "properties": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^per" - }, - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "permission": { - "type": "string" - }, - "patterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "metadata": { - "type": "object" - }, - "always": { - "type": "array", - "items": { - "type": "string" - } - }, - "tool": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg" - }, - "callID": { - "type": "string" - } - }, - "required": ["messageID", "callID"], - "additionalProperties": false - } - }, - "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, - { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["permission.replied"] - }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "requestID": { - "type": "string", - "pattern": "^per" - }, - "reply": { - "type": "string", - "enum": ["once", "always", "reject"] - } - }, - "required": ["sessionID", "requestID", "reply"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, { "type": "object", "properties": { @@ -14051,6 +14337,99 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.asked"] + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "permission": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["messageID", "callID"], + "additionalProperties": false + } + }, + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.replied"] + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "requestID": { + "type": "string", + "pattern": "^per" + }, + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["sessionID", "requestID", "reply"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, { "type": "object", "properties": { @@ -14922,6 +15301,88 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.v2.asked"] + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "action": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "save": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "source": { + "$ref": "#/components/schemas/PermissionV2Source" + } + }, + "required": ["id", "sessionID", "action", "resources"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.v2.replied"] + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "requestID": { + "type": "string", + "pattern": "^per" + }, + "reply": { + "$ref": "#/components/schemas/PermissionV2Reply" + } + }, + "required": ["sessionID", "requestID", "reply"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, { "type": "object", "properties": { @@ -16560,32 +17021,6 @@ "required": ["directory"], "additionalProperties": false }, - "PermissionAction": { - "type": "string", - "enum": ["allow", "deny", "ask"] - }, - "PermissionRule": { - "type": "object", - "properties": { - "permission": { - "type": "string" - }, - "pattern": { - "type": "string" - }, - "action": { - "$ref": "#/components/schemas/PermissionAction" - } - }, - "required": ["permission", "pattern", "action"], - "additionalProperties": false - }, - "PermissionRuleset": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PermissionRule" - } - }, "ProjectSummary": { "type": "object", "properties": { @@ -17489,8 +17924,7 @@ "type": "object", "properties": { "messageID": { - "type": "string", - "pattern": "^msg" + "type": "string" }, "callID": { "type": "string" @@ -18879,6 +19313,27 @@ "required": ["message", "isRetryable"], "additionalProperties": false }, + "PermissionV2Source": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["tool"] + }, + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["type", "messageID", "callID"], + "additionalProperties": false + }, + "PermissionV2Reply": { + "type": "string", + "enum": ["once", "always", "reject"] + }, "AuthOAuthCredential": { "type": "object", "properties": { @@ -21420,6 +21875,61 @@ "required": ["id", "name", "enabled", "env", "endpoint", "options"], "additionalProperties": false }, + "PermissionV2Request": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "action": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "save": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "source": { + "$ref": "#/components/schemas/PermissionV2Source" + } + }, + "required": ["id", "sessionID", "action", "resources"], + "additionalProperties": false + }, + "PermissionSavedInfo": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "projectID": { + "type": "string" + }, + "action": { + "type": "string" + }, + "resource": { + "type": "string" + } + }, + "required": ["id", "projectID", "action", "resource"], + "additionalProperties": false + }, "EventModels-devRefreshed": { "type": "object", "properties": { @@ -23080,100 +23590,6 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, - "EventPermissionAsked": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["permission.asked"] - }, - "properties": { - "type": "object", - "properties": { - "id": { - "type": "string", - "pattern": "^per" - }, - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "permission": { - "type": "string" - }, - "patterns": { - "type": "array", - "items": { - "type": "string" - } - }, - "metadata": { - "type": "object" - }, - "always": { - "type": "array", - "items": { - "type": "string" - } - }, - "tool": { - "type": "object", - "properties": { - "messageID": { - "type": "string", - "pattern": "^msg" - }, - "callID": { - "type": "string" - } - }, - "required": ["messageID", "callID"], - "additionalProperties": false - } - }, - "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, - "EventPermissionReplied": { - "type": "object", - "properties": { - "id": { - "type": "string" - }, - "type": { - "type": "string", - "enum": ["permission.replied"] - }, - "properties": { - "type": "object", - "properties": { - "sessionID": { - "type": "string", - "pattern": "^ses" - }, - "requestID": { - "type": "string", - "pattern": "^per" - }, - "reply": { - "type": "string", - "enum": ["once", "always", "reject"] - } - }, - "required": ["sessionID", "requestID", "reply"], - "additionalProperties": false - } - }, - "required": ["id", "type", "properties"], - "additionalProperties": false - }, "EventSessionDiff": { "type": "object", "properties": { @@ -23254,6 +23670,99 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventPermissionAsked": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.asked"] + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "permission": { + "type": "string" + }, + "patterns": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "always": { + "type": "array", + "items": { + "type": "string" + } + }, + "tool": { + "type": "object", + "properties": { + "messageID": { + "type": "string" + }, + "callID": { + "type": "string" + } + }, + "required": ["messageID", "callID"], + "additionalProperties": false + } + }, + "required": ["id", "sessionID", "permission", "patterns", "metadata", "always"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + "EventPermissionReplied": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.replied"] + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "requestID": { + "type": "string", + "pattern": "^per" + }, + "reply": { + "type": "string", + "enum": ["once", "always", "reject"] + } + }, + "required": ["sessionID", "requestID", "reply"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventQuestionAsked": { "type": "object", "properties": { @@ -23991,6 +24500,88 @@ "required": ["id", "type", "properties"], "additionalProperties": false }, + "EventPermissionV2Asked": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.v2.asked"] + }, + "properties": { + "type": "object", + "properties": { + "id": { + "type": "string", + "pattern": "^per" + }, + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "action": { + "type": "string" + }, + "resources": { + "type": "array", + "items": { + "type": "string" + } + }, + "save": { + "type": "array", + "items": { + "type": "string" + } + }, + "metadata": { + "type": "object" + }, + "source": { + "$ref": "#/components/schemas/PermissionV2Source" + } + }, + "required": ["id", "sessionID", "action", "resources"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, + "EventPermissionV2Replied": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "type": { + "type": "string", + "enum": ["permission.v2.replied"] + }, + "properties": { + "type": "object", + "properties": { + "sessionID": { + "type": "string", + "pattern": "^ses" + }, + "requestID": { + "type": "string", + "pattern": "^per" + }, + "reply": { + "$ref": "#/components/schemas/PermissionV2Reply" + } + }, + "required": ["sessionID", "requestID", "reply"], + "additionalProperties": false + } + }, + "required": ["id", "type", "properties"], + "additionalProperties": false + }, "EventAccountAdded": { "type": "object", "properties": { @@ -24172,6 +24763,18 @@ "name": "v2 providers", "description": "Experimental v2 provider routes." }, + { + "name": "v2 permissions", + "description": "Experimental v2 permission routes." + }, + { + "name": "v2 session permissions", + "description": "Experimental v2 session permission routes." + }, + { + "name": "v2 saved permissions", + "description": "Experimental v2 saved permission routes." + }, { "name": "tui", "description": "Experimental HttpApi TUI routes."