feat: bash commands

This commit is contained in:
adamdotdevin
2025-08-13 13:28:22 -05:00
parent e729eed34d
commit 1357319f6f
11 changed files with 1018 additions and 156 deletions

View File

@@ -1050,14 +1050,15 @@ export namespace Session {
}
await updatePart(part)
const app = App.info()
const process = exec(input.command, {
const proc = exec(input.command, {
cwd: app.path.cwd,
signal: abort.signal,
shell: process.env["SHELL"],
})
let output = ""
process.stdout?.on("data", (chunk) => {
proc.stdout?.on("data", (chunk) => {
output += chunk.toString()
if (part.state.status === "running") {
part.state.metadata = {
@@ -1068,7 +1069,7 @@ export namespace Session {
}
})
process.stderr?.on("data", (chunk) => {
proc.stderr?.on("data", (chunk) => {
output += chunk.toString()
if (part.state.status === "running") {
part.state.metadata = {
@@ -1080,7 +1081,7 @@ export namespace Session {
})
await new Promise<void>((resolve) => {
process.on("close", () => {
proc.on("close", () => {
resolve()
})
})

View File

@@ -1,4 +1,4 @@
configured_endpoints: 34
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-b86cf7bb8df4f60ebe8b8f51e281c8076cfdccc8554178c1b78beca4b025f0ff.yml
openapi_spec_hash: 47633b7481d91708643ea7b43fffffe6
config_hash: bd7f6435ed0c0005f373b5526c07a055
configured_endpoints: 36
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-9031231386199b2baadcaaed5b8df17899f8bc82efef4a74d7a0646fc035268a.yml
openapi_spec_hash: 8ef902a2a7039a4a6fde44ee7c26c87d
config_hash: 2b388a88fa9da825b43cbc25c2b349b5

View File

@@ -111,9 +111,11 @@ Response Types:
Methods:
- <code title="post /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.New">New</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</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="patch /session/{id}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Update">Update</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#SessionUpdateParams">SessionUpdateParams</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="get /session">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.List">List</a>(ctx <a href="https://pkg.go.dev/context">context</a>.<a href="https://pkg.go.dev/context#Context">Context</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}">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Delete">Delete</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}/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}/bash">client.Session.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#SessionService.Bash">Bash</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#SessionBashParams">SessionBashParams</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}/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>

View File

@@ -74,6 +74,8 @@ func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption
type Agent struct {
Mode AgentMode `json:"mode,required"`
Name string `json:"name,required"`
Options map[string]interface{} `json:"options,required"`
Permission AgentPermission `json:"permission,required"`
Tools map[string]bool `json:"tools,required"`
Description string `json:"description"`
Model AgentModel `json:"model"`
@@ -87,6 +89,8 @@ type Agent struct {
type agentJSON struct {
Mode apijson.Field
Name apijson.Field
Options apijson.Field
Permission apijson.Field
Tools apijson.Field
Description apijson.Field
Model apijson.Field
@@ -121,6 +125,78 @@ func (r AgentMode) IsKnown() bool {
return false
}
type AgentPermission struct {
Bash map[string]AgentPermissionBash `json:"bash,required"`
Edit AgentPermissionEdit `json:"edit,required"`
Webfetch AgentPermissionWebfetch `json:"webfetch"`
JSON agentPermissionJSON `json:"-"`
}
// agentPermissionJSON contains the JSON metadata for the struct [AgentPermission]
type agentPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *AgentPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r agentPermissionJSON) RawJSON() string {
return r.raw
}
type AgentPermissionBash string
const (
AgentPermissionBashAsk AgentPermissionBash = "ask"
AgentPermissionBashAllow AgentPermissionBash = "allow"
AgentPermissionBashDeny AgentPermissionBash = "deny"
)
func (r AgentPermissionBash) IsKnown() bool {
switch r {
case AgentPermissionBashAsk, AgentPermissionBashAllow, AgentPermissionBashDeny:
return true
}
return false
}
type AgentPermissionEdit string
const (
AgentPermissionEditAsk AgentPermissionEdit = "ask"
AgentPermissionEditAllow AgentPermissionEdit = "allow"
AgentPermissionEditDeny AgentPermissionEdit = "deny"
)
func (r AgentPermissionEdit) IsKnown() bool {
switch r {
case AgentPermissionEditAsk, AgentPermissionEditAllow, AgentPermissionEditDeny:
return true
}
return false
}
type AgentPermissionWebfetch string
const (
AgentPermissionWebfetchAsk AgentPermissionWebfetch = "ask"
AgentPermissionWebfetchAllow AgentPermissionWebfetch = "allow"
AgentPermissionWebfetchDeny AgentPermissionWebfetch = "deny"
)
func (r AgentPermissionWebfetch) IsKnown() bool {
switch r {
case AgentPermissionWebfetchAsk, AgentPermissionWebfetchAllow, AgentPermissionWebfetchDeny:
return true
}
return false
}
type AgentModel struct {
ModelID string `json:"modelID,required"`
ProviderID string `json:"providerID,required"`

View File

@@ -153,10 +153,12 @@ type ConfigAgentBuild struct {
Disable bool `json:"disable"`
Mode ConfigAgentBuildMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentBuildPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentBuildJSON `json:"-"`
}
@@ -167,6 +169,7 @@ type configAgentBuildJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -199,16 +202,134 @@ func (r ConfigAgentBuildMode) IsKnown() bool {
return false
}
type ConfigAgentBuildPermission struct {
Bash ConfigAgentBuildPermissionBashUnion `json:"bash"`
Edit ConfigAgentBuildPermissionEdit `json:"edit"`
Webfetch ConfigAgentBuildPermissionWebfetch `json:"webfetch"`
JSON configAgentBuildPermissionJSON `json:"-"`
}
// configAgentBuildPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentBuildPermission]
type configAgentBuildPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentBuildPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentBuildPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentBuildPermissionBashString] or
// [ConfigAgentBuildPermissionBashMap].
type ConfigAgentBuildPermissionBashUnion interface {
implementsConfigAgentBuildPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentBuildPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentBuildPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentBuildPermissionBashMap{}),
},
)
}
type ConfigAgentBuildPermissionBashString string
const (
ConfigAgentBuildPermissionBashStringAsk ConfigAgentBuildPermissionBashString = "ask"
ConfigAgentBuildPermissionBashStringAllow ConfigAgentBuildPermissionBashString = "allow"
ConfigAgentBuildPermissionBashStringDeny ConfigAgentBuildPermissionBashString = "deny"
)
func (r ConfigAgentBuildPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionBashStringAsk, ConfigAgentBuildPermissionBashStringAllow, ConfigAgentBuildPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentBuildPermissionBashString) implementsConfigAgentBuildPermissionBashUnion() {}
type ConfigAgentBuildPermissionBashMap map[string]ConfigAgentBuildPermissionBashMapItem
func (r ConfigAgentBuildPermissionBashMap) implementsConfigAgentBuildPermissionBashUnion() {}
type ConfigAgentBuildPermissionBashMapItem string
const (
ConfigAgentBuildPermissionBashMapAsk ConfigAgentBuildPermissionBashMapItem = "ask"
ConfigAgentBuildPermissionBashMapAllow ConfigAgentBuildPermissionBashMapItem = "allow"
ConfigAgentBuildPermissionBashMapDeny ConfigAgentBuildPermissionBashMapItem = "deny"
)
func (r ConfigAgentBuildPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionBashMapAsk, ConfigAgentBuildPermissionBashMapAllow, ConfigAgentBuildPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentBuildPermissionEdit string
const (
ConfigAgentBuildPermissionEditAsk ConfigAgentBuildPermissionEdit = "ask"
ConfigAgentBuildPermissionEditAllow ConfigAgentBuildPermissionEdit = "allow"
ConfigAgentBuildPermissionEditDeny ConfigAgentBuildPermissionEdit = "deny"
)
func (r ConfigAgentBuildPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionEditAsk, ConfigAgentBuildPermissionEditAllow, ConfigAgentBuildPermissionEditDeny:
return true
}
return false
}
type ConfigAgentBuildPermissionWebfetch string
const (
ConfigAgentBuildPermissionWebfetchAsk ConfigAgentBuildPermissionWebfetch = "ask"
ConfigAgentBuildPermissionWebfetchAllow ConfigAgentBuildPermissionWebfetch = "allow"
ConfigAgentBuildPermissionWebfetchDeny ConfigAgentBuildPermissionWebfetch = "deny"
)
func (r ConfigAgentBuildPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentBuildPermissionWebfetchAsk, ConfigAgentBuildPermissionWebfetchAllow, ConfigAgentBuildPermissionWebfetchDeny:
return true
}
return false
}
type ConfigAgentGeneral struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentGeneralMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentGeneralPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentGeneralJSON `json:"-"`
}
@@ -219,6 +340,7 @@ type configAgentGeneralJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -251,16 +373,134 @@ func (r ConfigAgentGeneralMode) IsKnown() bool {
return false
}
type ConfigAgentGeneralPermission struct {
Bash ConfigAgentGeneralPermissionBashUnion `json:"bash"`
Edit ConfigAgentGeneralPermissionEdit `json:"edit"`
Webfetch ConfigAgentGeneralPermissionWebfetch `json:"webfetch"`
JSON configAgentGeneralPermissionJSON `json:"-"`
}
// configAgentGeneralPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentGeneralPermission]
type configAgentGeneralPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentGeneralPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentGeneralPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentGeneralPermissionBashString] or
// [ConfigAgentGeneralPermissionBashMap].
type ConfigAgentGeneralPermissionBashUnion interface {
implementsConfigAgentGeneralPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentGeneralPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentGeneralPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentGeneralPermissionBashMap{}),
},
)
}
type ConfigAgentGeneralPermissionBashString string
const (
ConfigAgentGeneralPermissionBashStringAsk ConfigAgentGeneralPermissionBashString = "ask"
ConfigAgentGeneralPermissionBashStringAllow ConfigAgentGeneralPermissionBashString = "allow"
ConfigAgentGeneralPermissionBashStringDeny ConfigAgentGeneralPermissionBashString = "deny"
)
func (r ConfigAgentGeneralPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionBashStringAsk, ConfigAgentGeneralPermissionBashStringAllow, ConfigAgentGeneralPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentGeneralPermissionBashString) implementsConfigAgentGeneralPermissionBashUnion() {}
type ConfigAgentGeneralPermissionBashMap map[string]ConfigAgentGeneralPermissionBashMapItem
func (r ConfigAgentGeneralPermissionBashMap) implementsConfigAgentGeneralPermissionBashUnion() {}
type ConfigAgentGeneralPermissionBashMapItem string
const (
ConfigAgentGeneralPermissionBashMapAsk ConfigAgentGeneralPermissionBashMapItem = "ask"
ConfigAgentGeneralPermissionBashMapAllow ConfigAgentGeneralPermissionBashMapItem = "allow"
ConfigAgentGeneralPermissionBashMapDeny ConfigAgentGeneralPermissionBashMapItem = "deny"
)
func (r ConfigAgentGeneralPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionBashMapAsk, ConfigAgentGeneralPermissionBashMapAllow, ConfigAgentGeneralPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentGeneralPermissionEdit string
const (
ConfigAgentGeneralPermissionEditAsk ConfigAgentGeneralPermissionEdit = "ask"
ConfigAgentGeneralPermissionEditAllow ConfigAgentGeneralPermissionEdit = "allow"
ConfigAgentGeneralPermissionEditDeny ConfigAgentGeneralPermissionEdit = "deny"
)
func (r ConfigAgentGeneralPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionEditAsk, ConfigAgentGeneralPermissionEditAllow, ConfigAgentGeneralPermissionEditDeny:
return true
}
return false
}
type ConfigAgentGeneralPermissionWebfetch string
const (
ConfigAgentGeneralPermissionWebfetchAsk ConfigAgentGeneralPermissionWebfetch = "ask"
ConfigAgentGeneralPermissionWebfetchAllow ConfigAgentGeneralPermissionWebfetch = "allow"
ConfigAgentGeneralPermissionWebfetchDeny ConfigAgentGeneralPermissionWebfetch = "deny"
)
func (r ConfigAgentGeneralPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentGeneralPermissionWebfetchAsk, ConfigAgentGeneralPermissionWebfetchAllow, ConfigAgentGeneralPermissionWebfetchDeny:
return true
}
return false
}
type ConfigAgentPlan struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigAgentPlanMode `json:"mode"`
Model string `json:"model"`
Permission ConfigAgentPlanPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configAgentPlanJSON `json:"-"`
}
@@ -270,6 +510,7 @@ type configAgentPlanJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -302,6 +543,122 @@ func (r ConfigAgentPlanMode) IsKnown() bool {
return false
}
type ConfigAgentPlanPermission struct {
Bash ConfigAgentPlanPermissionBashUnion `json:"bash"`
Edit ConfigAgentPlanPermissionEdit `json:"edit"`
Webfetch ConfigAgentPlanPermissionWebfetch `json:"webfetch"`
JSON configAgentPlanPermissionJSON `json:"-"`
}
// configAgentPlanPermissionJSON contains the JSON metadata for the struct
// [ConfigAgentPlanPermission]
type configAgentPlanPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigAgentPlanPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configAgentPlanPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigAgentPlanPermissionBashString] or
// [ConfigAgentPlanPermissionBashMap].
type ConfigAgentPlanPermissionBashUnion interface {
implementsConfigAgentPlanPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigAgentPlanPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigAgentPlanPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigAgentPlanPermissionBashMap{}),
},
)
}
type ConfigAgentPlanPermissionBashString string
const (
ConfigAgentPlanPermissionBashStringAsk ConfigAgentPlanPermissionBashString = "ask"
ConfigAgentPlanPermissionBashStringAllow ConfigAgentPlanPermissionBashString = "allow"
ConfigAgentPlanPermissionBashStringDeny ConfigAgentPlanPermissionBashString = "deny"
)
func (r ConfigAgentPlanPermissionBashString) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionBashStringAsk, ConfigAgentPlanPermissionBashStringAllow, ConfigAgentPlanPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigAgentPlanPermissionBashString) implementsConfigAgentPlanPermissionBashUnion() {}
type ConfigAgentPlanPermissionBashMap map[string]ConfigAgentPlanPermissionBashMapItem
func (r ConfigAgentPlanPermissionBashMap) implementsConfigAgentPlanPermissionBashUnion() {}
type ConfigAgentPlanPermissionBashMapItem string
const (
ConfigAgentPlanPermissionBashMapAsk ConfigAgentPlanPermissionBashMapItem = "ask"
ConfigAgentPlanPermissionBashMapAllow ConfigAgentPlanPermissionBashMapItem = "allow"
ConfigAgentPlanPermissionBashMapDeny ConfigAgentPlanPermissionBashMapItem = "deny"
)
func (r ConfigAgentPlanPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionBashMapAsk, ConfigAgentPlanPermissionBashMapAllow, ConfigAgentPlanPermissionBashMapDeny:
return true
}
return false
}
type ConfigAgentPlanPermissionEdit string
const (
ConfigAgentPlanPermissionEditAsk ConfigAgentPlanPermissionEdit = "ask"
ConfigAgentPlanPermissionEditAllow ConfigAgentPlanPermissionEdit = "allow"
ConfigAgentPlanPermissionEditDeny ConfigAgentPlanPermissionEdit = "deny"
)
func (r ConfigAgentPlanPermissionEdit) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionEditAsk, ConfigAgentPlanPermissionEditAllow, ConfigAgentPlanPermissionEditDeny:
return true
}
return false
}
type ConfigAgentPlanPermissionWebfetch string
const (
ConfigAgentPlanPermissionWebfetchAsk ConfigAgentPlanPermissionWebfetch = "ask"
ConfigAgentPlanPermissionWebfetchAllow ConfigAgentPlanPermissionWebfetch = "allow"
ConfigAgentPlanPermissionWebfetchDeny ConfigAgentPlanPermissionWebfetch = "deny"
)
func (r ConfigAgentPlanPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigAgentPlanPermissionWebfetchAsk, ConfigAgentPlanPermissionWebfetchAllow, ConfigAgentPlanPermissionWebfetchDeny:
return true
}
return false
}
type ConfigExperimental struct {
Hook ConfigExperimentalHook `json:"hook"`
JSON configExperimentalJSON `json:"-"`
@@ -685,10 +1042,12 @@ type ConfigModeBuild struct {
Disable bool `json:"disable"`
Mode ConfigModeBuildMode `json:"mode"`
Model string `json:"model"`
Permission ConfigModeBuildPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configModeBuildJSON `json:"-"`
}
@@ -698,6 +1057,7 @@ type configModeBuildJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -730,16 +1090,134 @@ func (r ConfigModeBuildMode) IsKnown() bool {
return false
}
type ConfigModeBuildPermission struct {
Bash ConfigModeBuildPermissionBashUnion `json:"bash"`
Edit ConfigModeBuildPermissionEdit `json:"edit"`
Webfetch ConfigModeBuildPermissionWebfetch `json:"webfetch"`
JSON configModeBuildPermissionJSON `json:"-"`
}
// configModeBuildPermissionJSON contains the JSON metadata for the struct
// [ConfigModeBuildPermission]
type configModeBuildPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigModeBuildPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configModeBuildPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigModeBuildPermissionBashString] or
// [ConfigModeBuildPermissionBashMap].
type ConfigModeBuildPermissionBashUnion interface {
implementsConfigModeBuildPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigModeBuildPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigModeBuildPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigModeBuildPermissionBashMap{}),
},
)
}
type ConfigModeBuildPermissionBashString string
const (
ConfigModeBuildPermissionBashStringAsk ConfigModeBuildPermissionBashString = "ask"
ConfigModeBuildPermissionBashStringAllow ConfigModeBuildPermissionBashString = "allow"
ConfigModeBuildPermissionBashStringDeny ConfigModeBuildPermissionBashString = "deny"
)
func (r ConfigModeBuildPermissionBashString) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionBashStringAsk, ConfigModeBuildPermissionBashStringAllow, ConfigModeBuildPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigModeBuildPermissionBashString) implementsConfigModeBuildPermissionBashUnion() {}
type ConfigModeBuildPermissionBashMap map[string]ConfigModeBuildPermissionBashMapItem
func (r ConfigModeBuildPermissionBashMap) implementsConfigModeBuildPermissionBashUnion() {}
type ConfigModeBuildPermissionBashMapItem string
const (
ConfigModeBuildPermissionBashMapAsk ConfigModeBuildPermissionBashMapItem = "ask"
ConfigModeBuildPermissionBashMapAllow ConfigModeBuildPermissionBashMapItem = "allow"
ConfigModeBuildPermissionBashMapDeny ConfigModeBuildPermissionBashMapItem = "deny"
)
func (r ConfigModeBuildPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionBashMapAsk, ConfigModeBuildPermissionBashMapAllow, ConfigModeBuildPermissionBashMapDeny:
return true
}
return false
}
type ConfigModeBuildPermissionEdit string
const (
ConfigModeBuildPermissionEditAsk ConfigModeBuildPermissionEdit = "ask"
ConfigModeBuildPermissionEditAllow ConfigModeBuildPermissionEdit = "allow"
ConfigModeBuildPermissionEditDeny ConfigModeBuildPermissionEdit = "deny"
)
func (r ConfigModeBuildPermissionEdit) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionEditAsk, ConfigModeBuildPermissionEditAllow, ConfigModeBuildPermissionEditDeny:
return true
}
return false
}
type ConfigModeBuildPermissionWebfetch string
const (
ConfigModeBuildPermissionWebfetchAsk ConfigModeBuildPermissionWebfetch = "ask"
ConfigModeBuildPermissionWebfetchAllow ConfigModeBuildPermissionWebfetch = "allow"
ConfigModeBuildPermissionWebfetchDeny ConfigModeBuildPermissionWebfetch = "deny"
)
func (r ConfigModeBuildPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigModeBuildPermissionWebfetchAsk, ConfigModeBuildPermissionWebfetchAllow, ConfigModeBuildPermissionWebfetchDeny:
return true
}
return false
}
type ConfigModePlan struct {
// Description of when to use the agent
Description string `json:"description"`
Disable bool `json:"disable"`
Mode ConfigModePlanMode `json:"mode"`
Model string `json:"model"`
Permission ConfigModePlanPermission `json:"permission"`
Prompt string `json:"prompt"`
Temperature float64 `json:"temperature"`
Tools map[string]bool `json:"tools"`
TopP float64 `json:"top_p"`
ExtraFields map[string]interface{} `json:"-,extras"`
JSON configModePlanJSON `json:"-"`
}
@@ -749,6 +1227,7 @@ type configModePlanJSON struct {
Disable apijson.Field
Mode apijson.Field
Model apijson.Field
Permission apijson.Field
Prompt apijson.Field
Temperature apijson.Field
Tools apijson.Field
@@ -781,6 +1260,122 @@ func (r ConfigModePlanMode) IsKnown() bool {
return false
}
type ConfigModePlanPermission struct {
Bash ConfigModePlanPermissionBashUnion `json:"bash"`
Edit ConfigModePlanPermissionEdit `json:"edit"`
Webfetch ConfigModePlanPermissionWebfetch `json:"webfetch"`
JSON configModePlanPermissionJSON `json:"-"`
}
// configModePlanPermissionJSON contains the JSON metadata for the struct
// [ConfigModePlanPermission]
type configModePlanPermissionJSON struct {
Bash apijson.Field
Edit apijson.Field
Webfetch apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *ConfigModePlanPermission) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r configModePlanPermissionJSON) RawJSON() string {
return r.raw
}
// Union satisfied by [ConfigModePlanPermissionBashString] or
// [ConfigModePlanPermissionBashMap].
type ConfigModePlanPermissionBashUnion interface {
implementsConfigModePlanPermissionBashUnion()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*ConfigModePlanPermissionBashUnion)(nil)).Elem(),
"",
apijson.UnionVariant{
TypeFilter: gjson.String,
Type: reflect.TypeOf(ConfigModePlanPermissionBashString("")),
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ConfigModePlanPermissionBashMap{}),
},
)
}
type ConfigModePlanPermissionBashString string
const (
ConfigModePlanPermissionBashStringAsk ConfigModePlanPermissionBashString = "ask"
ConfigModePlanPermissionBashStringAllow ConfigModePlanPermissionBashString = "allow"
ConfigModePlanPermissionBashStringDeny ConfigModePlanPermissionBashString = "deny"
)
func (r ConfigModePlanPermissionBashString) IsKnown() bool {
switch r {
case ConfigModePlanPermissionBashStringAsk, ConfigModePlanPermissionBashStringAllow, ConfigModePlanPermissionBashStringDeny:
return true
}
return false
}
func (r ConfigModePlanPermissionBashString) implementsConfigModePlanPermissionBashUnion() {}
type ConfigModePlanPermissionBashMap map[string]ConfigModePlanPermissionBashMapItem
func (r ConfigModePlanPermissionBashMap) implementsConfigModePlanPermissionBashUnion() {}
type ConfigModePlanPermissionBashMapItem string
const (
ConfigModePlanPermissionBashMapAsk ConfigModePlanPermissionBashMapItem = "ask"
ConfigModePlanPermissionBashMapAllow ConfigModePlanPermissionBashMapItem = "allow"
ConfigModePlanPermissionBashMapDeny ConfigModePlanPermissionBashMapItem = "deny"
)
func (r ConfigModePlanPermissionBashMapItem) IsKnown() bool {
switch r {
case ConfigModePlanPermissionBashMapAsk, ConfigModePlanPermissionBashMapAllow, ConfigModePlanPermissionBashMapDeny:
return true
}
return false
}
type ConfigModePlanPermissionEdit string
const (
ConfigModePlanPermissionEditAsk ConfigModePlanPermissionEdit = "ask"
ConfigModePlanPermissionEditAllow ConfigModePlanPermissionEdit = "allow"
ConfigModePlanPermissionEditDeny ConfigModePlanPermissionEdit = "deny"
)
func (r ConfigModePlanPermissionEdit) IsKnown() bool {
switch r {
case ConfigModePlanPermissionEditAsk, ConfigModePlanPermissionEditAllow, ConfigModePlanPermissionEditDeny:
return true
}
return false
}
type ConfigModePlanPermissionWebfetch string
const (
ConfigModePlanPermissionWebfetchAsk ConfigModePlanPermissionWebfetch = "ask"
ConfigModePlanPermissionWebfetchAllow ConfigModePlanPermissionWebfetch = "allow"
ConfigModePlanPermissionWebfetchDeny ConfigModePlanPermissionWebfetch = "deny"
)
func (r ConfigModePlanPermissionWebfetch) IsKnown() bool {
switch r {
case ConfigModePlanPermissionWebfetchAsk, ConfigModePlanPermissionWebfetchAllow, ConfigModePlanPermissionWebfetchDeny:
return true
}
return false
}
type ConfigPermission struct {
Bash ConfigPermissionBashUnion `json:"bash"`
Edit ConfigPermissionEdit `json:"edit"`
@@ -897,10 +1492,10 @@ func (r ConfigPermissionWebfetch) IsKnown() bool {
}
type ConfigProvider struct {
Models map[string]ConfigProviderModel `json:"models,required"`
ID string `json:"id"`
API string `json:"api"`
Env []string `json:"env"`
Models map[string]ConfigProviderModel `json:"models"`
Name string `json:"name"`
Npm string `json:"npm"`
Options ConfigProviderOptions `json:"options"`
@@ -909,10 +1504,10 @@ type ConfigProvider struct {
// configProviderJSON contains the JSON metadata for the struct [ConfigProvider]
type configProviderJSON struct {
Models apijson.Field
ID apijson.Field
API apijson.Field
Env apijson.Field
Models apijson.Field
Name apijson.Field
Npm apijson.Field
Options apijson.Field
@@ -1138,6 +1733,8 @@ type KeybindsConfig struct {
SwitchModeReverse string `json:"switch_mode_reverse,required"`
// List available themes
ThemeList string `json:"theme_list,required"`
// Toggle thinking blocks
ThinkingBlocks string `json:"thinking_blocks,required"`
// Toggle tool details
ToolDetails string `json:"tool_details,required"`
JSON keybindsConfigJSON `json:"-"`
@@ -1184,6 +1781,7 @@ type keybindsConfigJSON struct {
SwitchMode apijson.Field
SwitchModeReverse apijson.Field
ThemeList apijson.Field
ThinkingBlocks apijson.Field
ToolDetails apijson.Field
raw string
ExtraFields map[string]apijson.Field

View File

@@ -46,6 +46,18 @@ func (r *SessionService) New(ctx context.Context, opts ...option.RequestOption)
return
}
// Update session properties
func (r *SessionService) Update(ctx context.Context, id string, body SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
return
}
// List all sessions
func (r *SessionService) List(ctx context.Context, opts ...option.RequestOption) (res *[]Session, err error) {
opts = append(r.Options[:], opts...)
@@ -66,18 +78,6 @@ func (r *SessionService) Delete(ctx context.Context, id string, opts ...option.R
return
}
// Update session properties
func (r *SessionService) Update(ctx context.Context, id string, body SessionUpdateParams, opts ...option.RequestOption) (res *Session, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPatch, path, body, &res, opts...)
return
}
// Abort a session
func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.RequestOption) (res *bool, err error) {
opts = append(r.Options[:], opts...)
@@ -90,6 +90,18 @@ func (r *SessionService) Abort(ctx context.Context, id string, opts ...option.Re
return
}
// Run a bash command
func (r *SessionService) Bash(ctx context.Context, id string, body SessionBashParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
opts = append(r.Options[:], opts...)
if id == "" {
err = errors.New("missing required id parameter")
return
}
path := fmt.Sprintf("session/%s/bash", id)
err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...)
return
}
// Create and send a new message to a session
func (r *SessionService) Chat(ctx context.Context, id string, body SessionChatParams, opts ...option.RequestOption) (res *AssistantMessage, err error) {
opts = append(r.Options[:], opts...)
@@ -976,10 +988,10 @@ type Part struct {
// This field can have the runtime type of [[]string].
Files interface{} `json:"files"`
Hash string `json:"hash"`
// This field can have the runtime type of [map[string]interface{}].
Metadata interface{} `json:"metadata"`
Mime string `json:"mime"`
Name string `json:"name"`
// This field can have the runtime type of [map[string]interface{}].
ProviderMetadata interface{} `json:"providerMetadata"`
Snapshot string `json:"snapshot"`
// This field can have the runtime type of [FilePartSource], [AgentPartSource].
Source interface{} `json:"source"`
@@ -1008,9 +1020,9 @@ type partJSON struct {
Filename apijson.Field
Files apijson.Field
Hash apijson.Field
Metadata apijson.Field
Mime apijson.Field
Name apijson.Field
ProviderMetadata apijson.Field
Snapshot apijson.Field
Source apijson.Field
State apijson.Field
@@ -1179,9 +1191,9 @@ type ReasoningPart struct {
MessageID string `json:"messageID,required"`
SessionID string `json:"sessionID,required"`
Text string `json:"text,required"`
Time ReasoningPartTime `json:"time,required"`
Type ReasoningPartType `json:"type,required"`
ProviderMetadata map[string]interface{} `json:"providerMetadata"`
Time ReasoningPartTime `json:"time"`
Metadata map[string]interface{} `json:"metadata"`
JSON reasoningPartJSON `json:"-"`
}
@@ -1191,9 +1203,9 @@ type reasoningPartJSON struct {
MessageID apijson.Field
SessionID apijson.Field
Text apijson.Field
Type apijson.Field
ProviderMetadata apijson.Field
Time apijson.Field
Type apijson.Field
Metadata apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
@@ -1208,20 +1220,6 @@ func (r reasoningPartJSON) RawJSON() string {
func (r ReasoningPart) implementsPart() {}
type ReasoningPartType string
const (
ReasoningPartTypeReasoning ReasoningPartType = "reasoning"
)
func (r ReasoningPartType) IsKnown() bool {
switch r {
case ReasoningPartTypeReasoning:
return true
}
return false
}
type ReasoningPartTime struct {
Start float64 `json:"start,required"`
End float64 `json:"end"`
@@ -1245,6 +1243,20 @@ func (r reasoningPartTimeJSON) RawJSON() string {
return r.raw
}
type ReasoningPartType string
const (
ReasoningPartTypeReasoning ReasoningPartType = "reasoning"
)
func (r ReasoningPartType) IsKnown() bool {
switch r {
case ReasoningPartTypeReasoning:
return true
}
return false
}
type Session struct {
ID string `json:"id,required"`
Time SessionTime `json:"time,required"`
@@ -2286,6 +2298,23 @@ func (r sessionMessagesResponseJSON) RawJSON() string {
return r.raw
}
type SessionUpdateParams struct {
Title param.Field[string] `json:"title"`
}
func (r SessionUpdateParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionBashParams struct {
Agent param.Field[string] `json:"agent,required"`
Command param.Field[string] `json:"command,required"`
}
func (r SessionBashParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionChatParams struct {
ModelID param.Field[string] `json:"modelID,required"`
Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
@@ -2368,11 +2397,3 @@ type SessionSummarizeParams struct {
func (r SessionSummarizeParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
type SessionUpdateParams struct {
Title param.Field[string] `json:"title"`
}
func (r SessionUpdateParams) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}

View File

@@ -35,6 +35,34 @@ func TestSessionNew(t *testing.T) {
}
}
func TestSessionUpdateWithOptionalParams(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.Update(
context.TODO(),
"id",
opencode.SessionUpdateParams{
Title: opencode.F("title"),
},
)
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 TestSessionList(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"
@@ -101,6 +129,35 @@ func TestSessionAbort(t *testing.T) {
}
}
func TestSessionBash(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.Bash(
context.TODO(),
"id",
opencode.SessionBashParams{
Agent: opencode.F("agent"),
Command: opencode.F("command"),
},
)
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 TestSessionChatWithOptionalParams(t *testing.T) {
t.Skip("skipped: tests are disabled for the time being")
baseURL := "http://localhost:4010"

View File

@@ -124,6 +124,8 @@ resources:
message: get /session/{id}/message/{messageID}
messages: get /session/{id}/message
chat: post /session/{id}/message
bash: post /session/{id}/bash
update: patch /session/{id}
revert: post /session/{id}/revert
unrevert: post /session/{id}/unrevert

View File

@@ -49,6 +49,7 @@ type App struct {
InitialSession *string
compactCancel context.CancelFunc
IsLeaderSequence bool
IsBashMode bool
}
func (a *App) Agent() *opencode.Agent {
@@ -79,6 +80,9 @@ type AgentSelectedMsg struct {
type SessionClearedMsg struct{}
type CompactSessionMsg struct{}
type SendPrompt = Prompt
type SendBash = struct {
Command string
}
type SetEditorContentMsg struct {
Text string
}
@@ -296,23 +300,41 @@ func (a *App) CycleRecentModel() (*App, tea.Cmd) {
}
nextIndex := 0
for i, recentModel := range recentModels {
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID && recentModel.ModelID == a.Model.ID {
if a.Provider != nil && a.Model != nil && recentModel.ProviderID == a.Provider.ID &&
recentModel.ModelID == a.Model.ID {
nextIndex = (i + 1) % len(recentModels)
break
}
}
for range recentModels {
currentRecentModel := recentModels[nextIndex%len(recentModels)]
provider, model := findModelByProviderAndModelID(a.Providers, currentRecentModel.ProviderID, currentRecentModel.ModelID)
provider, model := findModelByProviderAndModelID(
a.Providers,
currentRecentModel.ProviderID,
currentRecentModel.ModelID,
)
if provider != nil && model != nil {
a.Provider, a.Model = provider, model
a.State.AgentModel[a.Agent().Name] = AgentModel{ProviderID: provider.ID, ModelID: model.ID}
return a, tea.Sequence(a.SaveState(), toast.NewSuccessToast(fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name)))
a.State.AgentModel[a.Agent().Name] = AgentModel{
ProviderID: provider.ID,
ModelID: model.ID,
}
recentModels = append(recentModels[:nextIndex%len(recentModels)], recentModels[nextIndex%len(recentModels)+1:]...)
return a, tea.Sequence(
a.SaveState(),
toast.NewSuccessToast(
fmt.Sprintf("Switched to %s (%s)", model.Name, provider.Name),
),
)
}
recentModels = append(
recentModels[:nextIndex%len(recentModels)],
recentModels[nextIndex%len(recentModels)+1:]...)
if len(recentModels) < 2 {
a.State.RecentlyUsedModels = recentModels
return a, tea.Sequence(a.SaveState(), toast.NewInfoToast("Not enough valid recent models to cycle"))
return a, tea.Sequence(
a.SaveState(),
toast.NewInfoToast("Not enough valid recent models to cycle"),
)
}
}
a.State.RecentlyUsedModels = recentModels
@@ -464,10 +486,19 @@ func (a *App) InitializeProvider() tea.Cmd {
// Priority 3: Current agent's preferred model
if selectedProvider == nil && a.Agent().Model.ModelID != "" {
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil && model != nil {
if provider, model := findModelByProviderAndModelID(providers, a.Agent().Model.ProviderID, a.Agent().Model.ModelID); provider != nil &&
model != nil {
selectedProvider = provider
selectedModel = model
slog.Debug("Selected model from current agent", "provider", provider.ID, "model", model.ID, "agent", a.Agent().Name)
slog.Debug(
"Selected model from current agent",
"provider",
provider.ID,
"model",
model.ID,
"agent",
a.Agent().Name,
)
} else {
slog.Debug("Agent model not found", "provider", a.Agent().Model.ProviderID, "model", a.Agent().Model.ModelID, "agent", a.Agent().Name)
}
@@ -724,6 +755,38 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) {
return a, tea.Batch(cmds...)
}
func (a *App) SendBash(ctx context.Context, command string) (*App, tea.Cmd) {
var cmds []tea.Cmd
if a.Session.ID == "" {
session, err := a.CreateSession(ctx)
if err != nil {
return a, toast.NewErrorToast(err.Error())
}
a.Session = session
cmds = append(cmds, util.CmdHandler(SessionCreatedMsg{Session: session}))
}
cmds = append(cmds, func() tea.Msg {
_, err := a.Client.Session.Bash(
context.Background(),
a.Session.ID,
opencode.SessionBashParams{
Agent: opencode.F(a.Agent().Name),
Command: opencode.F(command),
},
)
if err != nil {
slog.Error("Failed to submit bash command", "error", err)
return toast.NewErrorToast("Failed to submit bash command")()
}
return nil
})
// The actual response will come through SSE
// For now, just return success
return a, tea.Batch(cmds...)
}
func (a *App) Cancel(ctx context.Context, sessionID string) error {
// Cancel any running compact operation
if a.compactCancel != nil {

View File

@@ -39,6 +39,7 @@ type EditorComponent interface {
Focus() (tea.Model, tea.Cmd)
Blur()
Submit() (tea.Model, tea.Cmd)
SubmitBash() (tea.Model, tea.Cmd)
Clear() (tea.Model, tea.Cmd)
Paste() (tea.Model, tea.Cmd)
Newline() (tea.Model, tea.Cmd)
@@ -342,6 +343,14 @@ func (m *editorComponent) Content() string {
Padding(0, 0, 0, 1).
Bold(true)
prompt := promptStyle.Render(">")
borderForeground := t.Border()
if m.app.IsLeaderSequence {
borderForeground = t.Accent()
}
if m.app.IsBashMode {
borderForeground = t.Secondary()
prompt = promptStyle.Render("!")
}
m.textarea.SetWidth(width - 6)
textarea := lipgloss.JoinHorizontal(
@@ -349,10 +358,6 @@ func (m *editorComponent) Content() string {
prompt,
m.textarea.View(),
)
borderForeground := t.Border()
if m.app.IsLeaderSequence {
borderForeground = t.Accent()
}
textarea = styles.NewStyle().
Background(t.BackgroundElement()).
Width(width).
@@ -489,6 +494,16 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
return m, tea.Batch(cmds...)
}
func (m *editorComponent) SubmitBash() (tea.Model, tea.Cmd) {
command := m.textarea.Value()
var cmds []tea.Cmd
updated, cmd := m.Clear()
m = updated.(*editorComponent)
cmds = append(cmds, cmd)
cmds = append(cmds, util.CmdHandler(app.SendBash{Command: command}))
return m, tea.Batch(cmds...)
}
func (m *editorComponent) Clear() (tea.Model, tea.Cmd) {
m.textarea.Reset()
m.historyIndex = -1

View File

@@ -151,6 +151,23 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
if a.app.IsBashMode {
if keyString == "backspace" && a.editor.Length() == 0 {
a.app.IsBashMode = false
return a, nil
}
if keyString == "enter" || keyString == "esc" || keyString == "ctrl+c" {
a.app.IsBashMode = false
if keyString == "enter" {
updated, cmd := a.editor.SubmitBash()
a.editor = updated.(chat.EditorComponent)
cmds = append(cmds, cmd)
}
return a, tea.Batch(cmds...)
}
}
// 1. Handle active modal
if a.modal != nil {
switch keyString {
@@ -189,7 +206,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// 3. Handle completions trigger
if keyString == "/" &&
!a.showCompletionDialog &&
a.editor.Value() == "" {
a.editor.Value() == "" &&
!a.app.IsBashMode {
a.showCompletionDialog = true
updated, cmd := a.editor.Update(msg)
@@ -207,7 +225,8 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// Handle file completions trigger
if keyString == "@" &&
!a.showCompletionDialog {
!a.showCompletionDialog &&
!a.app.IsBashMode {
a.showCompletionDialog = true
updated, cmd := a.editor.Update(msg)
@@ -223,6 +242,11 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, tea.Sequence(cmds...)
}
if keyString == "!" && a.editor.Value() == "" {
a.app.IsBashMode = true
return a, nil
}
if a.showCompletionDialog {
switch keyString {
case "tab", "enter", "esc", "ctrl+c", "up", "down", "ctrl+p", "ctrl+n":
@@ -378,6 +402,9 @@ func (a Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
a.showCompletionDialog = false
a.app, cmd = a.app.SendPrompt(context.Background(), msg)
cmds = append(cmds, cmd)
case app.SendBash:
a.app, cmd = a.app.SendBash(context.Background(), msg.Command)
cmds = append(cmds, cmd)
case app.SetEditorContentMsg:
// Set the editor content without sending
a.editor.SetValueWithAttachments(msg.Text)