diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index c1cf034d..a6c536b7 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -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((resolve) => { - process.on("close", () => { + proc.on("close", () => { resolve() }) }) diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml index 4db6fb4c..d9ba9daf 100644 --- a/packages/sdk/go/.stats.yml +++ b/packages/sdk/go/.stats.yml @@ -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 diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md index eeef1033..5f2bc310 100644 --- a/packages/sdk/go/api.md +++ b/packages/sdk/go/api.md @@ -111,9 +111,11 @@ Response Types: Methods: - client.Session.New(ctx context.Context) (opencode.Session, error) +- client.Session.Update(ctx context.Context, id string, body opencode.SessionUpdateParams) (opencode.Session, error) - client.Session.List(ctx context.Context) ([]opencode.Session, error) - client.Session.Delete(ctx context.Context, id string) (bool, error) - client.Session.Abort(ctx context.Context, id string) (bool, error) +- client.Session.Bash(ctx context.Context, id string, body opencode.SessionBashParams) (opencode.AssistantMessage, error) - client.Session.Chat(ctx context.Context, id string, body opencode.SessionChatParams) (opencode.AssistantMessage, error) - client.Session.Init(ctx context.Context, id string, body opencode.SessionInitParams) (bool, error) - client.Session.Message(ctx context.Context, id string, messageID string) (opencode.SessionMessageResponse, error) diff --git a/packages/sdk/go/app.go b/packages/sdk/go/app.go index 3a0161a1..2771f6bd 100644 --- a/packages/sdk/go/app.go +++ b/packages/sdk/go/app.go @@ -72,21 +72,25 @@ func (r *AppService) Providers(ctx context.Context, opts ...option.RequestOption } type Agent struct { - Mode AgentMode `json:"mode,required"` - Name string `json:"name,required"` - Tools map[string]bool `json:"tools,required"` - Description string `json:"description"` - Model AgentModel `json:"model"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - TopP float64 `json:"topP"` - JSON agentJSON `json:"-"` + 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"` + Prompt string `json:"prompt"` + Temperature float64 `json:"temperature"` + TopP float64 `json:"topP"` + JSON agentJSON `json:"-"` } // agentJSON contains the JSON metadata for the struct [Agent] 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"` diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go index ed3e3c5a..47d2fa58 100644 --- a/packages/sdk/go/config.go +++ b/packages/sdk/go/config.go @@ -149,15 +149,17 @@ func (r configAgentJSON) RawJSON() string { type ConfigAgentBuild struct { // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - Mode ConfigAgentBuildMode `json:"mode"` - Model string `json:"model"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - JSON configAgentBuildJSON `json:"-"` + Description string `json:"description"` + 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:"-"` } // configAgentBuildJSON contains the JSON metadata for the struct @@ -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,17 +202,135 @@ 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"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - JSON configAgentGeneralJSON `json:"-"` + 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:"-"` } // configAgentGeneralJSON contains the JSON metadata for the struct @@ -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,17 +373,135 @@ 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"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - JSON configAgentPlanJSON `json:"-"` + 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:"-"` } // configAgentPlanJSON contains the JSON metadata for the struct [ConfigAgentPlan] @@ -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:"-"` @@ -681,15 +1038,17 @@ func (r configModeJSON) RawJSON() string { type ConfigModeBuild struct { // Description of when to use the agent - Description string `json:"description"` - Disable bool `json:"disable"` - Mode ConfigModeBuildMode `json:"mode"` - Model string `json:"model"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - JSON configModeBuildJSON `json:"-"` + Description string `json:"description"` + 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:"-"` } // configModeBuildJSON contains the JSON metadata for the struct [ConfigModeBuild] @@ -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,17 +1090,135 @@ 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"` - Prompt string `json:"prompt"` - Temperature float64 `json:"temperature"` - Tools map[string]bool `json:"tools"` - TopP float64 `json:"top_p"` - JSON configModePlanJSON `json:"-"` + 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:"-"` } // configModePlanJSON contains the JSON metadata for the struct [ConfigModePlan] @@ -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 diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go index f377b2e4..b8c156b6 100644 --- a/packages/sdk/go/session.go +++ b/packages/sdk/go/session.go @@ -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,11 +988,11 @@ type Part struct { // This field can have the runtime type of [[]string]. Files interface{} `json:"files"` Hash string `json:"hash"` - 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"` + Metadata interface{} `json:"metadata"` + Mime string `json:"mime"` + Name string `json:"name"` + Snapshot string `json:"snapshot"` // This field can have the runtime type of [FilePartSource], [AgentPartSource]. Source interface{} `json:"source"` // This field can have the runtime type of [ToolPartState]. @@ -999,29 +1011,29 @@ type Part struct { // partJSON contains the JSON metadata for the struct [Part] type partJSON struct { - ID apijson.Field - MessageID apijson.Field - SessionID apijson.Field - Type apijson.Field - CallID apijson.Field - Cost apijson.Field - Filename apijson.Field - Files apijson.Field - Hash apijson.Field - Mime apijson.Field - Name apijson.Field - ProviderMetadata apijson.Field - Snapshot apijson.Field - Source apijson.Field - State apijson.Field - Synthetic apijson.Field - Text apijson.Field - Time apijson.Field - Tokens apijson.Field - Tool apijson.Field - URL apijson.Field - raw string - ExtraFields map[string]apijson.Field + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Type apijson.Field + CallID apijson.Field + Cost apijson.Field + Filename apijson.Field + Files apijson.Field + Hash apijson.Field + Metadata apijson.Field + Mime apijson.Field + Name apijson.Field + Snapshot apijson.Field + Source apijson.Field + State apijson.Field + Synthetic apijson.Field + Text apijson.Field + Time apijson.Field + Tokens apijson.Field + Tool apijson.Field + URL apijson.Field + raw string + ExtraFields map[string]apijson.Field } func (r partJSON) RawJSON() string { @@ -1175,27 +1187,27 @@ func (r PartType) IsKnown() bool { } type ReasoningPart struct { - ID string `json:"id,required"` - MessageID string `json:"messageID,required"` - SessionID string `json:"sessionID,required"` - Text string `json:"text,required"` - Type ReasoningPartType `json:"type,required"` - ProviderMetadata map[string]interface{} `json:"providerMetadata"` - Time ReasoningPartTime `json:"time"` - JSON reasoningPartJSON `json:"-"` + ID string `json:"id,required"` + 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"` + Metadata map[string]interface{} `json:"metadata"` + JSON reasoningPartJSON `json:"-"` } // reasoningPartJSON contains the JSON metadata for the struct [ReasoningPart] type reasoningPartJSON struct { - ID apijson.Field - MessageID apijson.Field - SessionID apijson.Field - Text apijson.Field - Type apijson.Field - ProviderMetadata apijson.Field - Time apijson.Field - raw string - ExtraFields map[string]apijson.Field + ID apijson.Field + MessageID apijson.Field + SessionID apijson.Field + Text apijson.Field + Time apijson.Field + Type apijson.Field + Metadata apijson.Field + raw string + ExtraFields map[string]apijson.Field } func (r *ReasoningPart) UnmarshalJSON(data []byte) (err error) { @@ -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) -} diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go index b8158f09..61a340e1 100644 --- a/packages/sdk/go/session_test.go +++ b/packages/sdk/go/session_test.go @@ -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" diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml index 0c85a2ce..eb0f633f 100644 --- a/packages/sdk/stainless/stainless.yml +++ b/packages/sdk/stainless/stainless.yml @@ -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 diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go index 48b7d67f..08a79e31 100644 --- a/packages/tui/internal/app/app.go +++ b/packages/tui/internal/app/app.go @@ -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, + } + 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:]...) + 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 { diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index 980d5262..a51f158c 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -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 diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index 0262f507..f5747084 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -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)