summaryrefslogtreecommitdiffhomepage
path: root/packages
diff options
context:
space:
mode:
authorDax <[email protected]>2025-07-18 13:42:50 -0400
committerGitHub <[email protected]>2025-07-18 13:42:50 -0400
commitd56dec4ba7867670d9a7dae2a535d38d59f24efb (patch)
treef10cde4e4aad0bc6d882266bdef96948251375f4 /packages
parentc952e9ae3d74dcdda2a4fbdfef19b42c49096026 (diff)
downloadopencode-d56dec4ba7867670d9a7dae2a535d38d59f24efb.tar.gz
opencode-d56dec4ba7867670d9a7dae2a535d38d59f24efb.zip
wip: optional IDs in api (#1128)
Diffstat (limited to 'packages')
-rw-r--r--packages/opencode/src/cli/cmd/run.ts2
-rw-r--r--packages/opencode/src/server/server.ts11
-rw-r--r--packages/opencode/src/session/index.ts54
-rw-r--r--packages/opencode/src/tool/task.ts2
-rw-r--r--packages/tui/internal/app/app.go28
-rw-r--r--packages/tui/internal/components/chat/editor.go6
-rw-r--r--packages/tui/sdk/.stats.yml6
-rw-r--r--packages/tui/sdk/api.md4
-rw-r--r--packages/tui/sdk/config.go23
-rw-r--r--packages/tui/sdk/session.go83
-rw-r--r--packages/tui/sdk/session_test.go23
11 files changed, 143 insertions, 99 deletions
diff --git a/packages/opencode/src/cli/cmd/run.ts b/packages/opencode/src/cli/cmd/run.ts
index 63e57b21f..fe15a0bd0 100644
--- a/packages/opencode/src/cli/cmd/run.ts
+++ b/packages/opencode/src/cli/cmd/run.ts
@@ -172,8 +172,6 @@ export const RunCommand = cmd({
parts: [
{
id: Identifier.ascending("part"),
- sessionID: session.id,
- messageID: messageID,
type: "text",
text: message,
},
diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts
index 6469b9bbc..4e6ebfbb3 100644
--- a/packages/opencode/src/server/server.ts
+++ b/packages/opencode/src/server/server.ts
@@ -451,16 +451,7 @@ export namespace Server {
id: z.string().openapi({ description: "Session ID" }),
}),
),
- zValidator(
- "json",
- z.object({
- messageID: z.string(),
- providerID: z.string(),
- modelID: z.string(),
- mode: z.string(),
- parts: z.union([MessageV2.FilePart, MessageV2.TextPart]).array(),
- }),
- ),
+ zValidator("json", Session.ChatInput.omit({ sessionID: true })),
async (c) => {
const sessionID = c.req.valid("param").id
const body = c.req.valid("json")
diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts
index db82e291e..2b358bec4 100644
--- a/packages/opencode/src/session/index.ts
+++ b/packages/opencode/src/session/index.ts
@@ -319,14 +319,39 @@ export namespace Session {
return part
}
- export async function chat(input: {
- sessionID: string
- messageID: string
- providerID: string
- modelID: string
- mode?: string
- parts: (MessageV2.TextPart | MessageV2.FilePart)[]
- }) {
+ export const ChatInput = z.object({
+ sessionID: Identifier.schema("session"),
+ messageID: Identifier.schema("message").optional(),
+ providerID: z.string(),
+ modelID: z.string(),
+ mode: z.string().optional(),
+ parts: z.array(
+ z.discriminatedUnion("type", [
+ MessageV2.TextPart.omit({
+ messageID: true,
+ sessionID: true,
+ })
+ .partial({
+ id: true,
+ })
+ .openapi({
+ ref: "TextPartInput",
+ }),
+ MessageV2.FilePart.omit({
+ messageID: true,
+ sessionID: true,
+ })
+ .partial({
+ id: true,
+ })
+ .openapi({
+ ref: "FilePartInput",
+ }),
+ ]),
+ ),
+ })
+
+ export async function chat(input: z.infer<typeof ChatInput>) {
const l = log.clone().tag("session", input.sessionID)
l.info("chatting")
@@ -384,7 +409,7 @@ export namespace Session {
if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id)
const userMsg: MessageV2.Info = {
- id: input.messageID,
+ id: input.messageID ?? Identifier.ascending("message"),
role: "user",
sessionID: input.sessionID,
time: {
@@ -490,7 +515,14 @@ export namespace Session {
]
}
}
- return [part]
+ return [
+ {
+ id: Identifier.ascending("part"),
+ ...part,
+ messageID: userMsg.id,
+ sessionID: input.sessionID,
+ },
+ ]
}),
).then((x) => x.flat())
@@ -1104,8 +1136,6 @@ export namespace Session {
parts: [
{
id: Identifier.ascending("part"),
- sessionID: input.sessionID,
- messageID: input.messageID,
type: "text",
text: PROMPT_INITIALIZE.replace("${path}", app.path.root),
},
diff --git a/packages/opencode/src/tool/task.ts b/packages/opencode/src/tool/task.ts
index 49b89495f..c26ca7e54 100644
--- a/packages/opencode/src/tool/task.ts
+++ b/packages/opencode/src/tool/task.ts
@@ -44,8 +44,6 @@ export const TaskTool = Tool.define({
parts: [
{
id: Identifier.ascending("part"),
- messageID,
- sessionID: session.id,
type: "text",
text: params.prompt,
},
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 57d9f98a3..8f7e27935 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -63,7 +63,7 @@ type SessionClearedMsg struct{}
type CompactSessionMsg struct{}
type SendMsg struct {
Text string
- Attachments []opencode.FilePartParam
+ Attachments []opencode.FilePartInputParam
}
type SetEditorContentMsg struct {
Text string
@@ -462,7 +462,7 @@ func (a *App) CreateSession(ctx context.Context) (*opencode.Session, error) {
func (a *App) SendChatMessage(
ctx context.Context,
text string,
- attachments []opencode.FilePartParam,
+ attachments []opencode.FilePartInputParam,
) (*App, tea.Cmd) {
var cmds []tea.Cmd
if a.Session.ID == "" {
@@ -511,22 +511,18 @@ func (a *App) SendChatMessage(
for _, part := range parts {
switch casted := part.(type) {
case opencode.TextPart:
- partsParam = append(partsParam, opencode.TextPartParam{
- ID: opencode.F(casted.ID),
- MessageID: opencode.F(casted.MessageID),
- SessionID: opencode.F(casted.SessionID),
- Type: opencode.F(casted.Type),
- Text: opencode.F(casted.Text),
+ partsParam = append(partsParam, opencode.TextPartInputParam{
+ ID: opencode.F(casted.ID),
+ Type: opencode.F(opencode.TextPartInputType(casted.Type)),
+ Text: opencode.F(casted.Text),
})
case opencode.FilePart:
- partsParam = append(partsParam, opencode.FilePartParam{
- ID: opencode.F(casted.ID),
- Mime: opencode.F(casted.Mime),
- MessageID: opencode.F(casted.MessageID),
- SessionID: opencode.F(casted.SessionID),
- Type: opencode.F(casted.Type),
- URL: opencode.F(casted.URL),
- Filename: opencode.F(casted.Filename),
+ partsParam = append(partsParam, opencode.FilePartInputParam{
+ ID: opencode.F(casted.ID),
+ Mime: opencode.F(casted.Mime),
+ Type: opencode.F(opencode.FilePartInputType(casted.Type)),
+ URL: opencode.F(casted.URL),
+ Filename: opencode.F(casted.Filename),
})
}
}
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 3db6fa80f..687553342 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -306,10 +306,10 @@ func (m *editorComponent) Submit() (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
attachments := m.textarea.GetAttachments()
- fileParts := make([]opencode.FilePartParam, 0)
+ fileParts := make([]opencode.FilePartInputParam, 0)
for _, attachment := range attachments {
- fileParts = append(fileParts, opencode.FilePartParam{
- Type: opencode.F(opencode.FilePartTypeFile),
+ fileParts = append(fileParts, opencode.FilePartInputParam{
+ Type: opencode.F(opencode.FilePartInputTypeFile),
Mime: opencode.F(attachment.MediaType),
URL: opencode.F(attachment.URL),
Filename: opencode.F(attachment.Filename),
diff --git a/packages/tui/sdk/.stats.yml b/packages/tui/sdk/.stats.yml
index 22b27ac6d..0f1eb3f11 100644
--- a/packages/tui/sdk/.stats.yml
+++ b/packages/tui/sdk/.stats.yml
@@ -1,4 +1,4 @@
configured_endpoints: 22
-openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-8792f91dd070f7b4ee671fc86e8a03976dc7fb6ee49f8c99ad989e1597003774.yml
-openapi_spec_hash: fe9dc3a074be560de0b97df9b5af2c1b
-config_hash: b7f3d9742335715c458494988498b183
+openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-d34620b462127c45497743c97fd3569f9e629d9fbd97c0707087eeddbd4b3de1.yml
+openapi_spec_hash: 23864c98d555350fe56f1d0e56f205c5
+config_hash: a8441af7cb2db855d79fd372ee3b9fb1
diff --git a/packages/tui/sdk/api.md b/packages/tui/sdk/api.md
index 1c3f5a5ee..68320cc04 100644
--- a/packages/tui/sdk/api.md
+++ b/packages/tui/sdk/api.md
@@ -77,8 +77,8 @@ Methods:
Params Types:
-- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#FilePartParam">FilePartParam</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#TextPartParam">TextPartParam</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#FilePartInputParam">FilePartInputParam</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#TextPartInputParam">TextPartInputParam</a>
Response Types:
diff --git a/packages/tui/sdk/config.go b/packages/tui/sdk/config.go
index cf89e2d9f..f54c6a75a 100644
--- a/packages/tui/sdk/config.go
+++ b/packages/tui/sdk/config.go
@@ -67,8 +67,8 @@ type Config struct {
Model string `json:"model"`
// Custom provider configurations and model overrides
Provider map[string]ConfigProvider `json:"provider"`
- // Control sharing behavior: 'auto' enables automatic sharing, 'disabled' disables
- // all sharing
+ // Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
+ // enables automatic sharing, 'disabled' disables all sharing
Share ConfigShare `json:"share"`
// Theme name to use for the interface
Theme string `json:"theme"`
@@ -206,6 +206,8 @@ type ConfigMcp struct {
Enabled bool `json:"enabled"`
// This field can have the runtime type of [map[string]string].
Environment interface{} `json:"environment"`
+ // This field can have the runtime type of [map[string]string].
+ Headers interface{} `json:"headers"`
// URL of the remote MCP server
URL string `json:"url"`
JSON configMcpJSON `json:"-"`
@@ -218,6 +220,7 @@ type configMcpJSON struct {
Command apijson.Field
Enabled apijson.Field
Environment apijson.Field
+ Headers apijson.Field
URL apijson.Field
raw string
ExtraFields map[string]apijson.Field
@@ -427,18 +430,19 @@ func (r configProviderModelsLimitJSON) RawJSON() string {
return r.raw
}
-// Control sharing behavior: 'auto' enables automatic sharing, 'disabled' disables
-// all sharing
+// Control sharing behavior:'manual' allows manual sharing via commands, 'auto'
+// enables automatic sharing, 'disabled' disables all sharing
type ConfigShare string
const (
+ ConfigShareManual ConfigShare = "manual"
ConfigShareAuto ConfigShare = "auto"
ConfigShareDisabled ConfigShare = "disabled"
)
func (r ConfigShare) IsKnown() bool {
switch r {
- case ConfigShareAuto, ConfigShareDisabled:
+ case ConfigShareManual, ConfigShareAuto, ConfigShareDisabled:
return true
}
return false
@@ -509,9 +513,9 @@ type KeybindsConfig struct {
SessionShare string `json:"session_share,required"`
// Unshare current session
SessionUnshare string `json:"session_unshare,required"`
- // Switch mode
+ // Next mode
SwitchMode string `json:"switch_mode,required"`
- // Switch mode reverse
+ // Previous Mode
SwitchModeReverse string `json:"switch_mode_reverse,required"`
// List available themes
ThemeList string `json:"theme_list,required"`
@@ -638,7 +642,9 @@ type McpRemoteConfig struct {
// URL of the remote MCP server
URL string `json:"url,required"`
// Enable or disable the MCP server on startup
- Enabled bool `json:"enabled"`
+ Enabled bool `json:"enabled"`
+ // Headers to send with the request
+ Headers map[string]string `json:"headers"`
JSON mcpRemoteConfigJSON `json:"-"`
}
@@ -647,6 +653,7 @@ type mcpRemoteConfigJSON struct {
Type apijson.Field
URL apijson.Field
Enabled apijson.Field
+ Headers apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
diff --git a/packages/tui/sdk/session.go b/packages/tui/sdk/session.go
index b62ade1f0..bfb2e2773 100644
--- a/packages/tui/sdk/session.go
+++ b/packages/tui/sdk/session.go
@@ -481,21 +481,33 @@ func (r FilePartType) IsKnown() bool {
return false
}
-type FilePartParam struct {
- ID param.Field[string] `json:"id,required"`
- MessageID param.Field[string] `json:"messageID,required"`
- Mime param.Field[string] `json:"mime,required"`
- SessionID param.Field[string] `json:"sessionID,required"`
- Type param.Field[FilePartType] `json:"type,required"`
- URL param.Field[string] `json:"url,required"`
- Filename param.Field[string] `json:"filename"`
+type FilePartInputParam struct {
+ Mime param.Field[string] `json:"mime,required"`
+ Type param.Field[FilePartInputType] `json:"type,required"`
+ URL param.Field[string] `json:"url,required"`
+ ID param.Field[string] `json:"id"`
+ Filename param.Field[string] `json:"filename"`
}
-func (r FilePartParam) MarshalJSON() (data []byte, err error) {
+func (r FilePartInputParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r FilePartParam) implementsSessionChatParamsPartUnion() {}
+func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {}
+
+type FilePartInputType string
+
+const (
+ FilePartInputTypeFile FilePartInputType = "file"
+)
+
+func (r FilePartInputType) IsKnown() bool {
+ switch r {
+ case FilePartInputTypeFile:
+ return true
+ }
+ return false
+}
type Message struct {
ID string `json:"id,required"`
@@ -1076,28 +1088,40 @@ func (r textPartTimeJSON) RawJSON() string {
return r.raw
}
-type TextPartParam struct {
- ID param.Field[string] `json:"id,required"`
- MessageID param.Field[string] `json:"messageID,required"`
- SessionID param.Field[string] `json:"sessionID,required"`
- Text param.Field[string] `json:"text,required"`
- Type param.Field[TextPartType] `json:"type,required"`
- Synthetic param.Field[bool] `json:"synthetic"`
- Time param.Field[TextPartTimeParam] `json:"time"`
+type TextPartInputParam struct {
+ Text param.Field[string] `json:"text,required"`
+ Type param.Field[TextPartInputType] `json:"type,required"`
+ ID param.Field[string] `json:"id"`
+ Synthetic param.Field[bool] `json:"synthetic"`
+ Time param.Field[TextPartInputTimeParam] `json:"time"`
}
-func (r TextPartParam) MarshalJSON() (data []byte, err error) {
+func (r TextPartInputParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
-func (r TextPartParam) implementsSessionChatParamsPartUnion() {}
+func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {}
+
+type TextPartInputType string
-type TextPartTimeParam struct {
+const (
+ TextPartInputTypeText TextPartInputType = "text"
+)
+
+func (r TextPartInputType) IsKnown() bool {
+ switch r {
+ case TextPartInputTypeText:
+ return true
+ }
+ return false
+}
+
+type TextPartInputTimeParam struct {
Start param.Field[float64] `json:"start,required"`
End param.Field[float64] `json:"end"`
}
-func (r TextPartTimeParam) MarshalJSON() (data []byte, err error) {
+func (r TextPartInputTimeParam) MarshalJSON() (data []byte, err error) {
return apijson.MarshalRoot(r)
}
@@ -1574,11 +1598,11 @@ func (r sessionMessagesResponseJSON) RawJSON() string {
}
type SessionChatParams struct {
- MessageID param.Field[string] `json:"messageID,required"`
- Mode param.Field[string] `json:"mode,required"`
ModelID param.Field[string] `json:"modelID,required"`
Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"`
ProviderID param.Field[string] `json:"providerID,required"`
+ MessageID param.Field[string] `json:"messageID"`
+ Mode param.Field[string] `json:"mode"`
}
func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
@@ -1586,10 +1610,8 @@ func (r SessionChatParams) MarshalJSON() (data []byte, err error) {
}
type SessionChatParamsPart struct {
- ID param.Field[string] `json:"id,required"`
- MessageID param.Field[string] `json:"messageID,required"`
- SessionID param.Field[string] `json:"sessionID,required"`
Type param.Field[SessionChatParamsPartsType] `json:"type,required"`
+ ID param.Field[string] `json:"id"`
Filename param.Field[string] `json:"filename"`
Mime param.Field[string] `json:"mime"`
Synthetic param.Field[bool] `json:"synthetic"`
@@ -1604,7 +1626,8 @@ func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) {
func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {}
-// Satisfied by [FilePartParam], [TextPartParam], [SessionChatParamsPart].
+// Satisfied by [TextPartInputParam], [FilePartInputParam],
+// [SessionChatParamsPart].
type SessionChatParamsPartUnion interface {
implementsSessionChatParamsPartUnion()
}
@@ -1612,13 +1635,13 @@ type SessionChatParamsPartUnion interface {
type SessionChatParamsPartsType string
const (
- SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text"
+ SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file"
)
func (r SessionChatParamsPartsType) IsKnown() bool {
switch r {
- case SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeText:
+ case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile:
return true
}
return false
diff --git a/packages/tui/sdk/session_test.go b/packages/tui/sdk/session_test.go
index c74a4a385..b96a98b9d 100644
--- a/packages/tui/sdk/session_test.go
+++ b/packages/tui/sdk/session_test.go
@@ -101,7 +101,7 @@ func TestSessionAbort(t *testing.T) {
}
}
-func TestSessionChat(t *testing.T) {
+func TestSessionChatWithOptionalParams(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 {
@@ -117,19 +117,20 @@ func TestSessionChat(t *testing.T) {
context.TODO(),
"id",
opencode.SessionChatParams{
- MessageID: opencode.F("messageID"),
- Mode: opencode.F("mode"),
- ModelID: opencode.F("modelID"),
- Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.FilePartParam{
+ ModelID: opencode.F("modelID"),
+ Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{
+ Text: opencode.F("text"),
+ Type: opencode.F(opencode.TextPartInputTypeText),
ID: opencode.F("id"),
- MessageID: opencode.F("messageID"),
- Mime: opencode.F("mime"),
- SessionID: opencode.F("sessionID"),
- Type: opencode.F(opencode.FilePartTypeFile),
- URL: opencode.F("url"),
- Filename: opencode.F("filename"),
+ Synthetic: opencode.F(true),
+ Time: opencode.F(opencode.TextPartInputTimeParam{
+ Start: opencode.F(0.000000),
+ End: opencode.F(0.000000),
+ }),
}}),
ProviderID: opencode.F("providerID"),
+ MessageID: opencode.F("msg"),
+ Mode: opencode.F("mode"),
},
)
if err != nil {