summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorDax Raad <[email protected]>2025-07-31 12:26:34 -0400
committerDax Raad <[email protected]>2025-07-31 12:26:47 -0400
commita5b20f973fb575ba25256c1b3cb13b03bea96fa1 (patch)
tree027dc732e00c49925abea17e012d89c54d6c5c7b
parent872b1e068f1307326d733663d7b79e0fb75d7232 (diff)
downloadopencode-a5b20f973fb575ba25256c1b3cb13b03bea96fa1.tar.gz
opencode-a5b20f973fb575ba25256c1b3cb13b03bea96fa1.zip
wip: refactor permissions
-rw-r--r--opencode.json4
-rw-r--r--packages/opencode/src/id/id.ts1
-rw-r--r--packages/opencode/src/permission/index.ts43
-rw-r--r--packages/opencode/src/tool/bash.ts5
-rw-r--r--packages/opencode/src/tool/edit.ts8
-rw-r--r--packages/opencode/src/tool/write.ts4
-rw-r--r--packages/sdk/go/.stats.yml8
-rw-r--r--packages/sdk/go/api.md12
-rw-r--r--packages/sdk/go/event.go59
-rw-r--r--packages/sdk/go/session.go43
-rw-r--r--packages/sdk/go/session_test.go26
-rw-r--r--packages/sdk/go/sessionpermission.go130
-rw-r--r--packages/sdk/go/sessionpermission_test.go43
-rw-r--r--packages/tui/go.mod2
-rw-r--r--packages/tui/internal/components/chat/messages.go4
15 files changed, 294 insertions, 98 deletions
diff --git a/opencode.json b/opencode.json
index 8efc57a7a..4789660da 100644
--- a/opencode.json
+++ b/opencode.json
@@ -28,5 +28,9 @@
"type": "local",
"command": ["opencode", "x", "@h1deya/mcp-server-weather"]
}
+ },
+ "permission": {
+ "edit": "ask",
+ "bash": "ask"
}
}
diff --git a/packages/opencode/src/id/id.ts b/packages/opencode/src/id/id.ts
index 6c1edd50d..4e3ba9d48 100644
--- a/packages/opencode/src/id/id.ts
+++ b/packages/opencode/src/id/id.ts
@@ -5,6 +5,7 @@ export namespace Identifier {
const prefixes = {
session: "ses",
message: "msg",
+ permission: "per",
user: "usr",
part: "prt",
} as const
diff --git a/packages/opencode/src/permission/index.ts b/packages/opencode/src/permission/index.ts
index e7b6854f6..a42396510 100644
--- a/packages/opencode/src/permission/index.ts
+++ b/packages/opencode/src/permission/index.ts
@@ -3,6 +3,7 @@ import { z } from "zod"
import { Bus } from "../bus"
import { Log } from "../util/log"
import { Installation } from "../installation"
+import { Identifier } from "../id/id"
export namespace Permission {
const log = Log.create({ service: "permission" })
@@ -10,9 +11,11 @@ export namespace Permission {
export const Info = z
.object({
id: z.string(),
+ type: z.string(),
+ pattern: z.string().optional(),
sessionID: z.string(),
messageID: z.string(),
- toolCallID: z.string().optional(),
+ callID: z.string().optional(),
title: z.string(),
metadata: z.record(z.any()),
time: z.object({
@@ -55,18 +58,19 @@ 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.toolCallID))
+ item.reject(new RejectedError(item.info.sessionID, item.info.id, item.info.callID))
}
}
},
)
export function ask(input: {
- id: Info["id"]
+ type: Info["type"]
+ title: Info["title"]
+ pattern?: Info["pattern"]
+ callID?: Info["callID"]
sessionID: Info["sessionID"]
messageID: Info["messageID"]
- toolCallID?: Info["toolCallID"]
- title: Info["title"]
metadata: Info["metadata"]
}) {
// TODO: dax, remove this when you're happy with permissions
@@ -75,24 +79,16 @@ export namespace Permission {
const { pending, approved } = state()
log.info("asking", {
sessionID: input.sessionID,
- permissionID: input.id,
messageID: input.messageID,
- toolCallID: input.toolCallID,
+ toolCallID: input.callID,
})
- if (approved[input.sessionID]?.[input.id]) {
- log.info("previously approved", {
- sessionID: input.sessionID,
- permissionID: input.id,
- messageID: input.messageID,
- toolCallID: input.toolCallID,
- })
- return
- }
+ if (approved[input.sessionID]?.[input.pattern ?? input.type]) return
const info: Info = {
- id: input.id,
+ id: Identifier.ascending("permission"),
+ type: input.type,
sessionID: input.sessionID,
messageID: input.messageID,
- toolCallID: input.toolCallID,
+ callID: input.callID,
title: input.title,
metadata: input.metadata,
time: {
@@ -101,18 +97,11 @@ export namespace Permission {
}
pending[input.sessionID] = pending[input.sessionID] || {}
return new Promise<void>((resolve, reject) => {
- pending[input.sessionID][input.id] = {
+ pending[input.sessionID][info.id] = {
info,
resolve,
reject,
}
- // setTimeout(() => {
- // respond({
- // sessionID: input.sessionID,
- // permissionID: input.id,
- // response: "always",
- // })
- // }, 1000)
Bus.publish(Event.Updated, info)
})
}
@@ -127,7 +116,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.toolCallID))
+ match.reject(new RejectedError(input.sessionID, input.permissionID, match.info.callID))
return
}
match.resolve()
diff --git a/packages/opencode/src/tool/bash.ts b/packages/opencode/src/tool/bash.ts
index 3032c52c0..ea845e4d2 100644
--- a/packages/opencode/src/tool/bash.ts
+++ b/packages/opencode/src/tool/bash.ts
@@ -108,10 +108,11 @@ export const BashTool = Tool.define("bash", {
const cfg = await Config.get()
if (cfg.permission?.bash === "ask")
await Permission.ask({
- id: "bash",
+ type: "bash",
+ pattern: params.command.split(" ").slice(0, 2).join(" ").trim(),
sessionID: ctx.sessionID,
messageID: ctx.messageID,
- toolCallID: ctx.toolCallID,
+ callID: ctx.toolCallID,
title: "Run this command: " + params.command,
metadata: {
command: params.command,
diff --git a/packages/opencode/src/tool/edit.ts b/packages/opencode/src/tool/edit.ts
index c8038a89a..588522cba 100644
--- a/packages/opencode/src/tool/edit.ts
+++ b/packages/opencode/src/tool/edit.ts
@@ -50,10 +50,10 @@ export const EditTool = Tool.define("edit", {
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
if (cfg.permission?.edit === "ask") {
await Permission.ask({
- id: "edit",
+ type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
- toolCallID: ctx.toolCallID,
+ callID: ctx.toolCallID,
title: "Edit this file: " + filePath,
metadata: {
filePath,
@@ -79,10 +79,10 @@ export const EditTool = Tool.define("edit", {
diff = trimDiff(createTwoFilesPatch(filePath, filePath, contentOld, contentNew))
if (cfg.permission?.edit === "ask") {
await Permission.ask({
- id: "edit",
+ type: "edit",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
- toolCallID: ctx.toolCallID,
+ callID: ctx.toolCallID,
title: "Edit this file: " + filePath,
metadata: {
filePath,
diff --git a/packages/opencode/src/tool/write.ts b/packages/opencode/src/tool/write.ts
index 3fde2524d..279b7fa6f 100644
--- a/packages/opencode/src/tool/write.ts
+++ b/packages/opencode/src/tool/write.ts
@@ -31,10 +31,10 @@ export const WriteTool = Tool.define("write", {
const cfg = await Config.get()
if (cfg.permission?.edit === "ask")
await Permission.ask({
- id: "write",
+ type: "write",
sessionID: ctx.sessionID,
messageID: ctx.messageID,
- toolCallID: ctx.toolCallID,
+ callID: ctx.toolCallID,
title: exists ? "Overwrite this file: " + filepath : "Create new file: " + filepath,
metadata: {
filePath: filepath,
diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml
index a42ec8ee2..471c90261 100644
--- a/packages/sdk/go/.stats.yml
+++ b/packages/sdk/go/.stats.yml
@@ -1,4 +1,4 @@
-configured_endpoints: 26
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-5bf6a39123d248d306490c1dee61b46ba113ea2c415a4de1a631c76462769c49.yml
-openapi_spec_hash: 3c5b25f121429281275ffd70c9d5cfe4
-config_hash: 1ae82c93499b9f0b9ba828b8919f9cb3
+configured_endpoints: 28
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-3fa00e84a92784c0e12cf47a49cf5ac4eb5556b5b3ad8769ad7b4e7e1bf1b01a.yml
+openapi_spec_hash: 5f98ce812d7feb00e6c2eb7a15dd8887
+config_hash: 7707d73ebbd7ad7042ab70466b39348d
diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md
index fb3db9c53..0291c776e 100644
--- a/packages/sdk/go/api.md
+++ b/packages/sdk/go/api.md
@@ -103,6 +103,7 @@ Response Types:
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStatePending">ToolStatePending</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#ToolStateRunning">ToolStateRunning</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#UserMessage">UserMessage</a>
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>
Methods:
@@ -113,6 +114,7 @@ Methods:
- <code title="post /session/{id}/abort">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Abort">Abort</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Chat">Chat</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionChatParams">SessionChatParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#AssistantMessage">AssistantMessage</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/init">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Init">Init</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionInitParams">SessionInitParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+- <code title="get /session/{id}/message/{messageID}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Message">Message</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, messageID <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessageResponse">SessionMessageResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="get /session/{id}/message">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Messages">Messages</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) ([]<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionMessagesResponse">SessionMessagesResponse</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/revert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Revert">Revert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionRevertParams">SessionRevertParams</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="post /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Share">Share</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
@@ -120,6 +122,16 @@ Methods:
- <code title="post /session/{id}/unrevert">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unrevert">Unrevert</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
- <code title="delete /session/{id}/share">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Unshare">Unshare</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>) (<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+## Permissions
+
+Response Types:
+
+- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Permission">Permission</a>
+
+Methods:
+
+- <code title="post /session/{id}/permissions/{permissionID}">client.Session.Permissions.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionService.Respond">Respond</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</a>, id <a href="https://pkg.go.dev/builtin#string">string</a>, permissionID <a href="https://pkg.go.dev/builtin#string">string</a>, body <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionPermissionRespondParams">SessionPermissionRespondParams</a>) (<a href="https://pkg.go.dev/builtin#bool">bool</a>, <a href="https://pkg.go.dev/builtin#error">error</a>)</code>
+
# Tui
Methods:
diff --git a/packages/sdk/go/event.go b/packages/sdk/go/event.go
index 5203ab23a..3c08b327e 100644
--- a/packages/sdk/go/event.go
+++ b/packages/sdk/go/event.go
@@ -54,8 +54,7 @@ type EventListResponse struct {
// [EventListResponseEventMessageRemovedProperties],
// [EventListResponseEventMessagePartUpdatedProperties],
// [EventListResponseEventMessagePartRemovedProperties],
- // [EventListResponseEventStorageWriteProperties],
- // [EventListResponseEventPermissionUpdatedProperties],
+ // [EventListResponseEventStorageWriteProperties], [Permission],
// [EventListResponseEventFileEditedProperties],
// [EventListResponseEventSessionUpdatedProperties],
// [EventListResponseEventSessionDeletedProperties],
@@ -643,9 +642,9 @@ func (r EventListResponseEventStorageWriteType) IsKnown() bool {
}
type EventListResponseEventPermissionUpdated struct {
- Properties EventListResponseEventPermissionUpdatedProperties `json:"properties,required"`
- Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
- JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
+ Properties Permission `json:"properties,required"`
+ Type EventListResponseEventPermissionUpdatedType `json:"type,required"`
+ JSON eventListResponseEventPermissionUpdatedJSON `json:"-"`
}
// eventListResponseEventPermissionUpdatedJSON contains the JSON metadata for the
@@ -667,56 +666,6 @@ func (r eventListResponseEventPermissionUpdatedJSON) RawJSON() string {
func (r EventListResponseEventPermissionUpdated) implementsEventListResponse() {}
-type EventListResponseEventPermissionUpdatedProperties struct {
- ID string `json:"id,required"`
- Metadata map[string]interface{} `json:"metadata,required"`
- SessionID string `json:"sessionID,required"`
- Time EventListResponseEventPermissionUpdatedPropertiesTime `json:"time,required"`
- Title string `json:"title,required"`
- JSON eventListResponseEventPermissionUpdatedPropertiesJSON `json:"-"`
-}
-
-// eventListResponseEventPermissionUpdatedPropertiesJSON contains the JSON metadata
-// for the struct [EventListResponseEventPermissionUpdatedProperties]
-type eventListResponseEventPermissionUpdatedPropertiesJSON struct {
- ID apijson.Field
- Metadata apijson.Field
- SessionID apijson.Field
- Time apijson.Field
- Title apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventPermissionUpdatedProperties) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventPermissionUpdatedPropertiesJSON) RawJSON() string {
- return r.raw
-}
-
-type EventListResponseEventPermissionUpdatedPropertiesTime struct {
- Created float64 `json:"created,required"`
- JSON eventListResponseEventPermissionUpdatedPropertiesTimeJSON `json:"-"`
-}
-
-// eventListResponseEventPermissionUpdatedPropertiesTimeJSON contains the JSON
-// metadata for the struct [EventListResponseEventPermissionUpdatedPropertiesTime]
-type eventListResponseEventPermissionUpdatedPropertiesTimeJSON struct {
- Created apijson.Field
- raw string
- ExtraFields map[string]apijson.Field
-}
-
-func (r *EventListResponseEventPermissionUpdatedPropertiesTime) UnmarshalJSON(data []byte) (err error) {
- return apijson.UnmarshalRoot(data, r)
-}
-
-func (r eventListResponseEventPermissionUpdatedPropertiesTimeJSON) RawJSON() string {
- return r.raw
-}
-
type EventListResponseEventPermissionUpdatedType string
const (
diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go
index 2598d51c6..d38c37e0e 100644
--- a/packages/sdk/go/session.go
+++ b/packages/sdk/go/session.go
@@ -24,7 +24,8 @@ import (
// automatically. You should not instantiate this service directly, and instead use
// the [NewSessionService] method instead.
type SessionService struct {
- Options []option.RequestOption
+ Options []option.RequestOption
+ Permissions *SessionPermissionService
}
// NewSessionService generates a new service that applies the given options to each
@@ -33,6 +34,7 @@ type SessionService struct {
func NewSessionService(opts ...option.RequestOption) (r *SessionService) {
r = &SessionService{}
r.Options = opts
+ r.Permissions = NewSessionPermissionService(opts...)
return
}
@@ -100,6 +102,22 @@ func (r *SessionService) Init(ctx context.Context, id string, body SessionInitPa
return
}
+// Get a message from a session
+func (r *SessionService) Message(ctx context.Context, id string, messageID string, opts ...option.RequestOption) (res *SessionMessageResponse, err error) {
+ opts = append(r.Options[:], opts...)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return
+ }
+ if messageID == "" {
+ err = errors.New("missing required messageID parameter")
+ return
+ }
+ path := fmt.Sprintf("session/%s/message/%s", id, messageID)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodGet, path, nil, &res, opts...)
+ return
+}
+
// List messages for a session
func (r *SessionService) Messages(ctx context.Context, id string, opts ...option.RequestOption) (res *[]SessionMessagesResponse, err error) {
opts = append(r.Options[:], opts...)
@@ -2012,6 +2030,29 @@ func (r userMessageTimeJSON) RawJSON() string {
return r.raw
}
+type SessionMessageResponse struct {
+ Info Message `json:"info,required"`
+ Parts []Part `json:"parts,required"`
+ JSON sessionMessageResponseJSON `json:"-"`
+}
+
+// sessionMessageResponseJSON contains the JSON metadata for the struct
+// [SessionMessageResponse]
+type sessionMessageResponseJSON struct {
+ Info apijson.Field
+ Parts apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *SessionMessageResponse) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r sessionMessageResponseJSON) RawJSON() string {
+ return r.raw
+}
+
type SessionMessagesResponse struct {
Info Message `json:"info,required"`
Parts []Part `json:"parts,required"`
diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go
index 295e9e7ce..ab9fbcf7b 100644
--- a/packages/sdk/go/session_test.go
+++ b/packages/sdk/go/session_test.go
@@ -176,6 +176,32 @@ func TestSessionInit(t *testing.T) {
}
}
+func TestSessionMessage(t *testing.T) {
+ t.Skip("skipped: tests are disabled for the time being")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Session.Message(
+ context.TODO(),
+ "id",
+ "messageID",
+ )
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
+
func TestSessionMessages(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
diff --git a/packages/sdk/go/sessionpermission.go b/packages/sdk/go/sessionpermission.go
new file mode 100644
index 000000000..85e55bd5e
--- /dev/null
+++ b/packages/sdk/go/sessionpermission.go
@@ -0,0 +1,130 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "net/http"
+
+ "github.com/sst/opencode-sdk-go/internal/apijson"
+ "github.com/sst/opencode-sdk-go/internal/param"
+ "github.com/sst/opencode-sdk-go/internal/requestconfig"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+// SessionPermissionService contains methods and other services that help with
+// interacting with the opencode API.
+//
+// Note, unlike clients, this service does not read variables from the environment
+// automatically. You should not instantiate this service directly, and instead use
+// the [NewSessionPermissionService] method instead.
+type SessionPermissionService struct {
+ Options []option.RequestOption
+}
+
+// NewSessionPermissionService generates a new service that applies the given
+// options to each request. These options are applied after the parent client's
+// options (if there is one), and before any request-specific options.
+func NewSessionPermissionService(opts ...option.RequestOption) (r *SessionPermissionService) {
+ r = &SessionPermissionService{}
+ r.Options = opts
+ return
+}
+
+// Respond to a permission request
+func (r *SessionPermissionService) Respond(ctx context.Context, id string, permissionID string, body SessionPermissionRespondParams, opts ...option.RequestOption) (res *bool, err error) {
+ opts = append(r.Options[:], opts...)
+ if id == "" {
+ err = errors.New("missing required id parameter")
+ return
+ }
+ if permissionID == "" {
+ err = errors.New("missing required permissionID parameter")
+ return
+ }
+ path := fmt.Sprintf("session/%s/permissions/%s", id, permissionID)
+ err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
+ return
+}
+
+type Permission struct {
+ ID string `json:"id,required"`
+ MessageID string `json:"messageID,required"`
+ Metadata map[string]interface{} `json:"metadata,required"`
+ SessionID string `json:"sessionID,required"`
+ Time PermissionTime `json:"time,required"`
+ Title string `json:"title,required"`
+ Type string `json:"type,required"`
+ CallID string `json:"callID"`
+ Pattern string `json:"pattern"`
+ JSON permissionJSON `json:"-"`
+}
+
+// permissionJSON contains the JSON metadata for the struct [Permission]
+type permissionJSON struct {
+ ID apijson.Field
+ MessageID apijson.Field
+ Metadata apijson.Field
+ SessionID apijson.Field
+ Time apijson.Field
+ Title apijson.Field
+ Type apijson.Field
+ CallID apijson.Field
+ Pattern apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *Permission) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r permissionJSON) RawJSON() string {
+ return r.raw
+}
+
+type PermissionTime struct {
+ Created float64 `json:"created,required"`
+ JSON permissionTimeJSON `json:"-"`
+}
+
+// permissionTimeJSON contains the JSON metadata for the struct [PermissionTime]
+type permissionTimeJSON struct {
+ Created apijson.Field
+ raw string
+ ExtraFields map[string]apijson.Field
+}
+
+func (r *PermissionTime) UnmarshalJSON(data []byte) (err error) {
+ return apijson.UnmarshalRoot(data, r)
+}
+
+func (r permissionTimeJSON) RawJSON() string {
+ return r.raw
+}
+
+type SessionPermissionRespondParams struct {
+ Response param.Field[SessionPermissionRespondParamsResponse] `json:"response,required"`
+}
+
+func (r SessionPermissionRespondParams) MarshalJSON() (data []byte, err error) {
+ return apijson.MarshalRoot(r)
+}
+
+type SessionPermissionRespondParamsResponse string
+
+const (
+ SessionPermissionRespondParamsResponseOnce SessionPermissionRespondParamsResponse = "once"
+ SessionPermissionRespondParamsResponseAlways SessionPermissionRespondParamsResponse = "always"
+ SessionPermissionRespondParamsResponseReject SessionPermissionRespondParamsResponse = "reject"
+)
+
+func (r SessionPermissionRespondParamsResponse) IsKnown() bool {
+ switch r {
+ case SessionPermissionRespondParamsResponseOnce, SessionPermissionRespondParamsResponseAlways, SessionPermissionRespondParamsResponseReject:
+ return true
+ }
+ return false
+}
diff --git a/packages/sdk/go/sessionpermission_test.go b/packages/sdk/go/sessionpermission_test.go
new file mode 100644
index 000000000..728976be4
--- /dev/null
+++ b/packages/sdk/go/sessionpermission_test.go
@@ -0,0 +1,43 @@
+// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
+
+package opencode_test
+
+import (
+ "context"
+ "errors"
+ "os"
+ "testing"
+
+ "github.com/sst/opencode-sdk-go"
+ "github.com/sst/opencode-sdk-go/internal/testutil"
+ "github.com/sst/opencode-sdk-go/option"
+)
+
+func TestSessionPermissionRespond(t *testing.T) {
+ t.Skip("skipped: tests are disabled for the time being")
+ baseURL := "http://localhost:4010"
+ if envURL, ok := os.LookupEnv("TEST_API_BASE_URL"); ok {
+ baseURL = envURL
+ }
+ if !testutil.CheckTestServer(t, baseURL) {
+ return
+ }
+ client := opencode.NewClient(
+ option.WithBaseURL(baseURL),
+ )
+ _, err := client.Session.Permissions.Respond(
+ context.TODO(),
+ "id",
+ "permissionID",
+ opencode.SessionPermissionRespondParams{
+ Response: opencode.F(opencode.SessionPermissionRespondParamsResponseOnce),
+ },
+ )
+ if err != nil {
+ var apierr *opencode.Error
+ if errors.As(err, &apierr) {
+ t.Log(string(apierr.DumpRequest(true)))
+ }
+ t.Fatalf("err should be nil: %s", err.Error())
+ }
+}
diff --git a/packages/tui/go.mod b/packages/tui/go.mod
index bf2812caa..6dff3e7eb 100644
--- a/packages/tui/go.mod
+++ b/packages/tui/go.mod
@@ -24,7 +24,7 @@ require (
replace (
github.com/charmbracelet/x/input => ./input
- github.com/sst/opencode-sdk-go => ./sdk
+ github.com/sst/opencode-sdk-go => ../sdk/go
)
require golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 96ea8241d..412d2c4c0 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -469,7 +469,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
}
permission := opencode.Permission{}
- if m.app.CurrentPermission.ToolCallID == part.CallID {
+ if m.app.CurrentPermission.CallID == part.CallID {
permission = m.app.CurrentPermission
}
@@ -640,7 +640,7 @@ func (m *messagesComponent) renderView() tea.Cmd {
slog.Error("Failed to get message from child session", "error", err)
} else {
for _, part := range response.Parts {
- if part.CallID == m.app.CurrentPermission.ToolCallID {
+ if part.CallID == m.app.CurrentPermission.CallID {
content := renderToolDetails(
m.app,
part.AsUnion().(opencode.ToolPart),