diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 48a9a25f..b81b357a 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -593,10 +593,10 @@ export namespace Server { }, ) .post( - "/session/:id/bash", + "/session/:id/shell", describeRoute({ - description: "Run a bash command", - operationId: "session.bash", + description: "Run a shell command", + operationId: "session.shell", responses: { 200: { description: "Created message", @@ -618,7 +618,7 @@ export namespace Server { async (c) => { const sessionID = c.req.valid("param").id const body = c.req.valid("json") - const msg = await Session.bash({ ...body, sessionID }) + const msg = await Session.shell({ ...body, sessionID }) return c.json(msg) }, ) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index a6c536b7..ea51ce02 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -1,5 +1,5 @@ import path from "path" -import { exec } from "child_process" +import { spawn } from "child_process" import { Decimal } from "decimal.js" import { z, ZodSchema } from "zod" import { @@ -670,7 +670,7 @@ export namespace Session { const lastSummary = msgs.findLast((msg) => msg.info.role === "assistant" && msg.info.summary === true) if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id) - if (msgs.length === 1 && !session.parentID && isDefaultTitle(session.title)) { + if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) { const small = (await Provider.getSmallModel(input.providerID)) ?? model generateText({ maxOutputTokens: small.info.reasoning ? 1024 : 20, @@ -1005,7 +1005,7 @@ export namespace Session { command: z.string(), }) export type CommandInput = z.infer - export async function bash(input: CommandInput) { + export async function shell(input: CommandInput) { using abort = lock(input.sessionID) const msg: MessageV2.Assistant = { id: Identifier.ascending("message"), @@ -1050,10 +1050,18 @@ export namespace Session { } await updatePart(part) const app = App.info() - const proc = exec(input.command, { + const script = ` + [[ -f ~/.zshrc ]] && source ~/.zshrc 2>/dev/null || true + [[ -f ~/.bashrc ]] && source ~/.bashrc 2>/dev/null || true + eval "${input.command}" + ` + const proc = spawn(process.env["SHELL"] ?? "bash", ["-c", "-l", script], { cwd: app.path.cwd, signal: abort.signal, - shell: process.env["SHELL"], + env: { + ...process.env, + TERM: "dumb", + }, }) let output = "" diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml index d9ba9daf..9f1e1380 100644 --- a/packages/sdk/go/.stats.yml +++ b/packages/sdk/go/.stats.yml @@ -1,4 +1,4 @@ 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 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-a881262c7de4ab59bdfbfc6e30a23c47dee465d7270ffb867b760b0103aff8ed.yml +openapi_spec_hash: 7dbb6f96f5c26a25c849e50298f58586 +config_hash: 8d85a768523cff92b85ef06c443d49fa diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md index 5f2bc310..79a67e42 100644 --- a/packages/sdk/go/api.md +++ b/packages/sdk/go/api.md @@ -115,13 +115,13 @@ Methods: - 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) - client.Session.Messages(ctx context.Context, id string) ([]opencode.SessionMessagesResponse, error) - client.Session.Revert(ctx context.Context, id string, body opencode.SessionRevertParams) (opencode.Session, error) - client.Session.Share(ctx context.Context, id string) (opencode.Session, error) +- client.Session.Shell(ctx context.Context, id string, body opencode.SessionShellParams) (opencode.AssistantMessage, error) - client.Session.Summarize(ctx context.Context, id string, body opencode.SessionSummarizeParams) (bool, error) - client.Session.Unrevert(ctx context.Context, id string) (opencode.Session, error) - client.Session.Unshare(ctx context.Context, id string) (opencode.Session, error) diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go index b8c156b6..76a9d46f 100644 --- a/packages/sdk/go/session.go +++ b/packages/sdk/go/session.go @@ -90,18 +90,6 @@ 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...) @@ -178,6 +166,18 @@ func (r *SessionService) Share(ctx context.Context, id string, opts ...option.Re return } +// Run a shell command +func (r *SessionService) Shell(ctx context.Context, id string, body SessionShellParams, 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/shell", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, body, &res, opts...) + return +} + // Summarize the session func (r *SessionService) Summarize(ctx context.Context, id string, body SessionSummarizeParams, opts ...option.RequestOption) (res *bool, err error) { opts = append(r.Options[:], opts...) @@ -2306,15 +2306,6 @@ 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"` @@ -2389,6 +2380,15 @@ func (r SessionRevertParams) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } +type SessionShellParams struct { + Agent param.Field[string] `json:"agent,required"` + Command param.Field[string] `json:"command,required"` +} + +func (r SessionShellParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + type SessionSummarizeParams struct { ModelID param.Field[string] `json:"modelID,required"` ProviderID param.Field[string] `json:"providerID,required"` diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go index 61a340e1..cf4a851c 100644 --- a/packages/sdk/go/session_test.go +++ b/packages/sdk/go/session_test.go @@ -129,35 +129,6 @@ 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" @@ -332,6 +303,35 @@ func TestSessionShare(t *testing.T) { } } +func TestSessionShell(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.Shell( + context.TODO(), + "id", + opencode.SessionShellParams{ + 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 TestSessionSummarize(t *testing.T) { t.Skip("skipped: tests are disabled for the time being") baseURL := "http://localhost:4010" diff --git a/packages/sdk/stainless/generate.ts b/packages/sdk/stainless/generate.ts index 0a766d0d..6b1877f6 100755 --- a/packages/sdk/stainless/generate.ts +++ b/packages/sdk/stainless/generate.ts @@ -7,7 +7,7 @@ console.log("=== Generating Stainless SDK ===") console.log(process.cwd()) await $`rm -rf go` -await $`bun run ../../opencode/src/index.ts generate > openapi.json` +await $`bun run --conditions=development ../../opencode/src/index.ts generate > openapi.json` await $`stl builds create --branch dev --pull --allow-empty --+target go` await $`rm -rf ../go` diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml index eb0f633f..db4afd8d 100644 --- a/packages/sdk/stainless/stainless.yml +++ b/packages/sdk/stainless/stainless.yml @@ -124,7 +124,7 @@ resources: message: get /session/{id}/message/{messageID} messages: get /session/{id}/message chat: post /session/{id}/message - bash: post /session/{id}/bash + shell: post /session/{id}/shell 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 08a79e31..5e9a5a37 100644 --- a/packages/tui/internal/app/app.go +++ b/packages/tui/internal/app/app.go @@ -767,10 +767,10 @@ func (a *App) SendBash(ctx context.Context, command string) (*App, tea.Cmd) { } cmds = append(cmds, func() tea.Msg { - _, err := a.Client.Session.Bash( + _, err := a.Client.Session.Shell( context.Background(), a.Session.ID, - opencode.SessionBashParams{ + opencode.SessionShellParams{ Agent: opencode.F(a.Agent().Name), Command: opencode.F(command), },