diff --git a/packages/opencode/src/provider/provider.ts b/packages/opencode/src/provider/provider.ts index 3d6ea16e5..184c2c950 100644 --- a/packages/opencode/src/provider/provider.ts +++ b/packages/opencode/src/provider/provider.ts @@ -153,7 +153,10 @@ export namespace Provider { options: Record } } = {} - const models = new Map() + const models = new Map< + string, + { providerID: string; modelID: string; info: ModelsDev.Model; language: LanguageModel } + >() const sdk = new Map() log.info("init") @@ -362,10 +365,14 @@ export namespace Provider { const language = provider.getModel ? await provider.getModel(sdk, modelID) : sdk.languageModel(modelID) log.info("found", { providerID, modelID }) s.models.set(key, { + providerID, + modelID, info, language, }) return { + modelID, + providerID, info, language, } diff --git a/packages/opencode/src/server/server.ts b/packages/opencode/src/server/server.ts index 15afccf49..01ade6d1b 100644 --- a/packages/opencode/src/server/server.ts +++ b/packages/opencode/src/server/server.ts @@ -53,6 +53,9 @@ export namespace Server { const app = new Hono() export const App = app .onError((err, c) => { + log.error("failed", { + error: err, + }) if (err instanceof NamedError) { return c.json(err.toObject(), { status: 400, @@ -606,7 +609,7 @@ export namespace Server { "/session/:id/message", describeRoute({ description: "Create and send a new message to a session", - operationId: "session.chat", + operationId: "session.prompt", responses: { 200: { description: "Created message", @@ -629,11 +632,11 @@ export namespace Server { id: z.string().openapi({ description: "Session ID" }), }), ), - zValidator("json", Session.ChatInput.omit({ sessionID: true })), + zValidator("json", Session.PromptInput.omit({ sessionID: true })), async (c) => { const sessionID = c.req.valid("param").id const body = c.req.valid("json") - const msg = await Session.chat({ ...body, sessionID }) + const msg = await Session.prompt({ ...body, sessionID }) return c.json(msg) }, ) diff --git a/packages/opencode/src/session/index.ts b/packages/opencode/src/session/index.ts index 654607e74..d6848e95b 100644 --- a/packages/opencode/src/session/index.ts +++ b/packages/opencode/src/session/index.ts @@ -364,11 +364,15 @@ export namespace Session { return part } - export const ChatInput = z.object({ + export const PromptInput = z.object({ sessionID: Identifier.schema("session"), messageID: Identifier.schema("message").optional(), - providerID: z.string(), - modelID: z.string(), + model: z + .object({ + providerID: z.string(), + modelID: z.string(), + }) + .optional(), agent: z.string().optional(), system: z.string().optional(), tools: z.record(z.boolean()).optional(), @@ -407,10 +411,10 @@ export namespace Session { ]), ), }) - export type ChatInput = z.infer + export type ChatInput = z.infer - export async function chat( - input: z.infer, + export async function prompt( + input: z.infer, ): Promise<{ info: MessageV2.Assistant; parts: MessageV2.Part[] }> { const l = log.clone().tag("session", input.sessionID) l.info("chatting") @@ -652,7 +656,16 @@ export namespace Session { }) } - const model = await Provider.getModel(input.providerID, input.modelID) + const agent = await Agent.get(inputAgent) + const model = await (async () => { + if (input.model) { + return input.model + } + if (agent.model) { + return agent.model + } + return Provider.defaultModel() + })().then((x) => Provider.getModel(x.providerID, x.modelID)) let msgs = await messages(input.sessionID) const previous = msgs.filter((x) => x.info.role === "assistant").at(-1)?.info as MessageV2.Assistant @@ -667,10 +680,10 @@ export namespace Session { await summarize({ sessionID: input.sessionID, - providerID: input.providerID, - modelID: input.modelID, + providerID: model.providerID, + modelID: model.info.id, }) - return chat(input) + return prompt(input) } } using abort = lock(input.sessionID) @@ -679,17 +692,17 @@ export namespace Session { if (lastSummary) msgs = msgs.filter((msg) => msg.info.id >= lastSummary.info.id) if (msgs.filter((m) => m.info.role === "user").length === 1 && !session.parentID && isDefaultTitle(session.title)) { - const small = (await Provider.getSmallModel(input.providerID)) ?? model + const small = (await Provider.getSmallModel(model.providerID)) ?? model generateText({ maxOutputTokens: small.info.reasoning ? 1024 : 20, providerOptions: { - [input.providerID]: { + [model.providerID]: { ...small.info.options, - ...ProviderTransform.options(input.providerID, small.info.id, input.sessionID), + ...ProviderTransform.options(small.providerID, small.modelID, input.sessionID), }, }, messages: [ - ...SystemPrompt.title(input.providerID).map( + ...SystemPrompt.title(model.providerID).map( (x): ModelMessage => ({ role: "system", content: x, @@ -724,7 +737,6 @@ export namespace Session { }) } - const agent = await Agent.get(inputAgent) if (agent.name === "plan") { msgs.at(-1)?.parts.push({ id: Identifier.ascending("part"), @@ -747,12 +759,12 @@ export namespace Session { synthetic: true, }) } - let system = SystemPrompt.header(input.providerID) + let system = SystemPrompt.header(model.providerID) system.push( ...(() => { if (input.system) return [input.system] if (agent.prompt) return [agent.prompt] - return SystemPrompt.provider(input.modelID) + return SystemPrompt.provider(model.modelID) })(), ) system.push(...(await SystemPrompt.environment())) @@ -777,8 +789,8 @@ export namespace Session { reasoning: 0, cache: { read: 0, write: 0 }, }, - modelID: input.modelID, - providerID: input.providerID, + modelID: model.modelID, + providerID: model.providerID, time: { created: Date.now(), }, @@ -796,10 +808,10 @@ export namespace Session { const enabledTools = pipe( agent.tools, - mergeDeep(await ToolRegistry.enabled(input.providerID, input.modelID, agent)), + mergeDeep(await ToolRegistry.enabled(model.providerID, model.modelID, agent)), mergeDeep(input.tools ?? {}), ) - for (const item of await ToolRegistry.tools(input.providerID, input.modelID)) { + for (const item of await ToolRegistry.tools(model.providerID, model.modelID)) { if (Wildcard.all(item.id, enabledTools) === false) continue tools[item.id] = tool({ id: item.id as any, @@ -909,16 +921,16 @@ export namespace Session { "chat.params", { model: model.info, - provider: await Provider.getProvider(input.providerID), + provider: await Provider.getProvider(model.providerID), message: userMsg, }, { temperature: model.info.temperature - ? (agent.temperature ?? ProviderTransform.temperature(input.providerID, input.modelID)) + ? (agent.temperature ?? ProviderTransform.temperature(model.providerID, model.modelID)) : undefined, - topP: agent.topP ?? ProviderTransform.topP(input.providerID, input.modelID), + topP: agent.topP ?? ProviderTransform.topP(model.providerID, model.modelID), options: { - ...ProviderTransform.options(input.providerID, input.modelID, input.sessionID), + ...ProviderTransform.options(model.providerID, model.modelID, input.sessionID), ...model.info.options, ...agent.options, }, @@ -962,8 +974,8 @@ export namespace Session { reasoning: 0, cache: { read: 0, write: 0 }, }, - modelID: input.modelID, - providerID: input.providerID, + modelID: model.modelID, + providerID: model.providerID, mode: inputAgent, time: { created: Date.now(), @@ -987,7 +999,7 @@ export namespace Session { } }, headers: - input.providerID === "opencode" + model.providerID === "opencode" ? { "x-opencode-session": input.sessionID, "x-opencode-request": userMsg.id, @@ -1010,7 +1022,7 @@ export namespace Session { return false }, providerOptions: { - [input.providerID]: params.options, + [model.providerID]: params.options, }, temperature: params.temperature, topP: params.topP, @@ -1031,7 +1043,7 @@ export namespace Session { async transformParams(args) { if (args.type === "stream") { // @ts-expect-error - args.params.prompt = ProviderTransform.message(args.params.prompt, input.providerID, input.modelID) + args.params.prompt = ProviderTransform.message(args.params.prompt, model.providerID, model.modelID) } return args.params }, @@ -1044,7 +1056,7 @@ export namespace Session { const unprocessed = queued.find((x) => !x.processed) if (unprocessed) { unprocessed.processed = true - return chat(unprocessed.input) + return prompt(unprocessed.input) } for (const item of queued) { item.callback(result) @@ -1220,16 +1232,9 @@ export namespace Session { const fileRegex = /@([^\s]+)/g export async function command(input: CommandInput) { + log.info("command", input) const command = await Command.get(input.command) const agent = command.agent ?? input.agent ?? "build" - const fmtModel = (model: { providerID: string; modelID: string }) => `${model.providerID}/${model.modelID}` - - const model = - command.model ?? - (command.agent && (await Agent.get(command.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ?? - input.model ?? - (input.agent && (await Agent.get(input.agent).then((x) => (x.model ? fmtModel(x.model) : undefined)))) ?? - fmtModel(await Provider.defaultModel()) let template = command.template.replace("$ARGUMENTS", input.arguments) @@ -1273,10 +1278,18 @@ export namespace Session { }) } - return chat({ + return prompt({ sessionID: input.sessionID, messageID: input.messageID, - ...Provider.parseModel(model!), + model: (() => { + if (input.model) { + return Provider.parseModel(input.model) + } + if (command.model) { + return Provider.parseModel(command.model) + } + return undefined + })(), agent, parts, }) @@ -1643,7 +1656,7 @@ export namespace Session { const filtered = msgs.filter((msg) => !lastSummary || msg.info.id >= lastSummary.info.id) const model = await Provider.getModel(input.providerID, input.modelID) const system = [ - ...SystemPrompt.summarize(input.providerID), + ...SystemPrompt.summarize(model.providerID), ...(await SystemPrompt.environment()), ...(await SystemPrompt.custom()), ] @@ -1661,7 +1674,7 @@ export namespace Session { summary: true, cost: 0, modelID: input.modelID, - providerID: input.providerID, + providerID: model.providerID, tokens: { input: 0, output: 0, @@ -1770,11 +1783,13 @@ export namespace Session { providerID: string messageID: string }) { - await Session.chat({ + await Session.prompt({ sessionID: input.sessionID, messageID: input.messageID, - providerID: input.providerID, - modelID: input.modelID, + model: { + providerID: input.providerID, + modelID: input.modelID, + }, parts: [ { id: Identifier.ascending("part"), diff --git a/packages/sdk/go/.release-please-manifest.json b/packages/sdk/go/.release-please-manifest.json index 2aca35ae2..1b77f506d 100644 --- a/packages/sdk/go/.release-please-manifest.json +++ b/packages/sdk/go/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "0.5.0" + ".": "0.7.0" } \ No newline at end of file diff --git a/packages/sdk/go/.stats.yml b/packages/sdk/go/.stats.yml index 024d6225d..8d606c8fc 100644 --- a/packages/sdk/go/.stats.yml +++ b/packages/sdk/go/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-24d6bfcc66bba2e3a826fdad7e48a474c5aa9193a6bb89dc8ab1feb1f3d9bf72.yml -openapi_spec_hash: db8b553192d9027e1f9254096406cfc2 -config_hash: 1b0d220e033fe9f683abf7048e7ad076 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-57001be06439e5325d0cb6837dd39d04fb7e438386668dcb67b4375ce6fabcf5.yml +openapi_spec_hash: 5c3f5b2a345e5e6cda17865e3bae1fd2 +config_hash: 026ef000d34bf2f930e7b41e77d2d3ff diff --git a/packages/sdk/go/CHANGELOG.md b/packages/sdk/go/CHANGELOG.md index 2a92cdf76..5ecd78294 100644 --- a/packages/sdk/go/CHANGELOG.md +++ b/packages/sdk/go/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 0.7.0 (2025-09-01) + +Full Changelog: [v0.6.0...v0.7.0](https://github.com/sst/opencode-sdk-go/compare/v0.6.0...v0.7.0) + +### Features + +* **api:** api update ([64bb1b1](https://github.com/sst/opencode-sdk-go/commit/64bb1b1ee0cbe153abc6fb7bd9703b47911724d4)) + +## 0.6.0 (2025-09-01) + +Full Changelog: [v0.5.0...v0.6.0](https://github.com/sst/opencode-sdk-go/compare/v0.5.0...v0.6.0) + +### Features + +* **api:** api update ([928e384](https://github.com/sst/opencode-sdk-go/commit/928e3843355f96899f046f002b84372281dad0c8)) + ## 0.5.0 (2025-08-31) Full Changelog: [v0.4.0...v0.5.0](https://github.com/sst/opencode-sdk-go/compare/v0.4.0...v0.5.0) diff --git a/packages/sdk/go/README.md b/packages/sdk/go/README.md index 880886bd9..cf61f6ced 100644 --- a/packages/sdk/go/README.md +++ b/packages/sdk/go/README.md @@ -24,7 +24,7 @@ Or to pin the version: ```sh -go get -u 'github.com/sst/opencode-sdk-go@v0.5.0' +go get -u 'github.com/sst/opencode-sdk-go@v0.7.0' ``` diff --git a/packages/sdk/go/api.md b/packages/sdk/go/api.md index ba2a388b8..02ac42b36 100644 --- a/packages/sdk/go/api.md +++ b/packages/sdk/go/api.md @@ -143,10 +143,10 @@ Response Types: - opencode.ToolStatePending - opencode.ToolStateRunning - opencode.UserMessage -- opencode.SessionChatResponse - opencode.SessionCommandResponse - opencode.SessionMessageResponse - opencode.SessionMessagesResponse +- opencode.SessionPromptResponse Methods: @@ -155,13 +155,13 @@ Methods: - client.Session.List(ctx context.Context, query opencode.SessionListParams) ([]opencode.Session, error) - client.Session.Delete(ctx context.Context, id string, body opencode.SessionDeleteParams) (bool, error) - client.Session.Abort(ctx context.Context, id string, body opencode.SessionAbortParams) (bool, error) -- client.Session.Chat(ctx context.Context, id string, params opencode.SessionChatParams) (opencode.SessionChatResponse, error) - client.Session.Children(ctx context.Context, id string, query opencode.SessionChildrenParams) ([]opencode.Session, error) - client.Session.Command(ctx context.Context, id string, params opencode.SessionCommandParams) (opencode.SessionCommandResponse, error) - client.Session.Get(ctx context.Context, id string, query opencode.SessionGetParams) (opencode.Session, error) - client.Session.Init(ctx context.Context, id string, params opencode.SessionInitParams) (bool, error) - client.Session.Message(ctx context.Context, id string, messageID string, query opencode.SessionMessageParams) (opencode.SessionMessageResponse, error) - client.Session.Messages(ctx context.Context, id string, query opencode.SessionMessagesParams) ([]opencode.SessionMessagesResponse, error) +- client.Session.Prompt(ctx context.Context, id string, params opencode.SessionPromptParams) (opencode.SessionPromptResponse, error) - client.Session.Revert(ctx context.Context, id string, params opencode.SessionRevertParams) (opencode.Session, error) - client.Session.Share(ctx context.Context, id string, body opencode.SessionShareParams) (opencode.Session, error) - client.Session.Shell(ctx context.Context, id string, params opencode.SessionShellParams) (opencode.AssistantMessage, error) diff --git a/packages/sdk/go/config.go b/packages/sdk/go/config.go index 4c92b0ea1..5469fb29e 100644 --- a/packages/sdk/go/config.go +++ b/packages/sdk/go/config.go @@ -13,6 +13,7 @@ import ( "github.com/sst/opencode-sdk-go/internal/param" "github.com/sst/opencode-sdk-go/internal/requestconfig" "github.com/sst/opencode-sdk-go/option" + "github.com/sst/opencode-sdk-go/shared" "github.com/tidwall/gjson" ) @@ -1650,10 +1651,13 @@ func (r configProviderModelsLimitJSON) RawJSON() string { } type ConfigProviderOptions struct { - APIKey string `json:"apiKey"` - BaseURL string `json:"baseURL"` - ExtraFields map[string]interface{} `json:"-,extras"` - JSON configProviderOptionsJSON `json:"-"` + APIKey string `json:"apiKey"` + BaseURL string `json:"baseURL"` + // Timeout in milliseconds for requests to this provider. Default is 300000 (5 + // minutes). Set to false to disable timeout. + Timeout ConfigProviderOptionsTimeoutUnion `json:"timeout"` + ExtraFields map[string]interface{} `json:"-,extras"` + JSON configProviderOptionsJSON `json:"-"` } // configProviderOptionsJSON contains the JSON metadata for the struct @@ -1661,6 +1665,7 @@ type ConfigProviderOptions struct { type configProviderOptionsJSON struct { APIKey apijson.Field BaseURL apijson.Field + Timeout apijson.Field raw string ExtraFields map[string]apijson.Field } @@ -1673,6 +1678,33 @@ func (r configProviderOptionsJSON) RawJSON() string { return r.raw } +// Timeout in milliseconds for requests to this provider. Default is 300000 (5 +// minutes). Set to false to disable timeout. +// +// Union satisfied by [shared.UnionInt] or [shared.UnionBool]. +type ConfigProviderOptionsTimeoutUnion interface { + ImplementsConfigProviderOptionsTimeoutUnion() +} + +func init() { + apijson.RegisterUnion( + reflect.TypeOf((*ConfigProviderOptionsTimeoutUnion)(nil)).Elem(), + "", + apijson.UnionVariant{ + TypeFilter: gjson.Number, + Type: reflect.TypeOf(shared.UnionInt(0)), + }, + apijson.UnionVariant{ + TypeFilter: gjson.True, + Type: reflect.TypeOf(shared.UnionBool(false)), + }, + apijson.UnionVariant{ + TypeFilter: gjson.False, + Type: reflect.TypeOf(shared.UnionBool(false)), + }, + ) +} + // Control sharing behavior:'manual' allows manual sharing via commands, 'auto' // enables automatic sharing, 'disabled' disables all sharing type ConfigShare string diff --git a/packages/sdk/go/internal/version.go b/packages/sdk/go/internal/version.go index 67c4d4094..79301ae47 100644 --- a/packages/sdk/go/internal/version.go +++ b/packages/sdk/go/internal/version.go @@ -2,4 +2,4 @@ package internal -const PackageVersion = "0.5.0" // x-release-please-version +const PackageVersion = "0.7.0" // x-release-please-version diff --git a/packages/sdk/go/path.go b/packages/sdk/go/path.go index 6d80e85fe..63e502626 100644 --- a/packages/sdk/go/path.go +++ b/packages/sdk/go/path.go @@ -42,15 +42,19 @@ func (r *PathService) Get(ctx context.Context, query PathGetParams, opts ...opti } type Path struct { - Config string `json:"config,required"` - State string `json:"state,required"` - JSON pathJSON `json:"-"` + Config string `json:"config,required"` + Directory string `json:"directory,required"` + State string `json:"state,required"` + Worktree string `json:"worktree,required"` + JSON pathJSON `json:"-"` } // pathJSON contains the JSON metadata for the struct [Path] type pathJSON struct { Config apijson.Field + Directory apijson.Field State apijson.Field + Worktree apijson.Field raw string ExtraFields map[string]apijson.Field } diff --git a/packages/sdk/go/session.go b/packages/sdk/go/session.go index 11b1338a1..88d71b575 100644 --- a/packages/sdk/go/session.go +++ b/packages/sdk/go/session.go @@ -92,18 +92,6 @@ func (r *SessionService) Abort(ctx context.Context, id string, body SessionAbort return } -// Create and send a new message to a session -func (r *SessionService) Chat(ctx context.Context, id string, params SessionChatParams, opts ...option.RequestOption) (res *SessionChatResponse, err error) { - opts = append(r.Options[:], opts...) - if id == "" { - err = errors.New("missing required id parameter") - return - } - path := fmt.Sprintf("session/%s/message", id) - err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) - return -} - // Get a session's children func (r *SessionService) Children(ctx context.Context, id string, query SessionChildrenParams, opts ...option.RequestOption) (res *[]Session, err error) { opts = append(r.Options[:], opts...) @@ -180,6 +168,18 @@ func (r *SessionService) Messages(ctx context.Context, id string, query SessionM return } +// Create and send a new message to a session +func (r *SessionService) Prompt(ctx context.Context, id string, params SessionPromptParams, opts ...option.RequestOption) (res *SessionPromptResponse, err error) { + opts = append(r.Options[:], opts...) + if id == "" { + err = errors.New("missing required id parameter") + return + } + path := fmt.Sprintf("session/%s/message", id) + err = requestconfig.ExecuteNewRequest(ctx, http.MethodPost, path, params, &res, opts...) + return +} + // Revert a message func (r *SessionService) Revert(ctx context.Context, id string, params SessionRevertParams, opts ...option.RequestOption) (res *Session, err error) { opts = append(r.Options[:], opts...) @@ -333,7 +333,7 @@ func (r AgentPartInputParam) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } -func (r AgentPartInputParam) implementsSessionChatParamsPartUnion() {} +func (r AgentPartInputParam) implementsSessionPromptParamsPartUnion() {} type AgentPartInputType string @@ -709,7 +709,7 @@ func (r FilePartInputParam) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } -func (r FilePartInputParam) implementsSessionChatParamsPartUnion() {} +func (r FilePartInputParam) implementsSessionPromptParamsPartUnion() {} type FilePartInputType string @@ -1820,7 +1820,7 @@ func (r TextPartInputParam) MarshalJSON() (data []byte, err error) { return apijson.MarshalRoot(r) } -func (r TextPartInputParam) implementsSessionChatParamsPartUnion() {} +func (r TextPartInputParam) implementsSessionPromptParamsPartUnion() {} type TextPartInputType string @@ -2296,29 +2296,6 @@ func (r userMessageTimeJSON) RawJSON() string { return r.raw } -type SessionChatResponse struct { - Info AssistantMessage `json:"info,required"` - Parts []Part `json:"parts,required"` - JSON sessionChatResponseJSON `json:"-"` -} - -// sessionChatResponseJSON contains the JSON metadata for the struct -// [SessionChatResponse] -type sessionChatResponseJSON struct { - Info apijson.Field - Parts apijson.Field - raw string - ExtraFields map[string]apijson.Field -} - -func (r *SessionChatResponse) UnmarshalJSON(data []byte) (err error) { - return apijson.UnmarshalRoot(data, r) -} - -func (r sessionChatResponseJSON) RawJSON() string { - return r.raw -} - type SessionCommandResponse struct { Info AssistantMessage `json:"info,required"` Parts []Part `json:"parts,required"` @@ -2388,6 +2365,29 @@ func (r sessionMessagesResponseJSON) RawJSON() string { return r.raw } +type SessionPromptResponse struct { + Info AssistantMessage `json:"info,required"` + Parts []Part `json:"parts,required"` + JSON sessionPromptResponseJSON `json:"-"` +} + +// sessionPromptResponseJSON contains the JSON metadata for the struct +// [SessionPromptResponse] +type sessionPromptResponseJSON struct { + Info apijson.Field + Parts apijson.Field + raw string + ExtraFields map[string]apijson.Field +} + +func (r *SessionPromptResponse) UnmarshalJSON(data []byte) (err error) { + return apijson.UnmarshalRoot(data, r) +} + +func (r sessionPromptResponseJSON) RawJSON() string { + return r.raw +} + type SessionNewParams struct { Directory param.Field[string] `query:"directory"` ParentID param.Field[string] `json:"parentID"` @@ -2459,70 +2459,6 @@ func (r SessionAbortParams) URLQuery() (v url.Values) { }) } -type SessionChatParams struct { - ModelID param.Field[string] `json:"modelID,required"` - Parts param.Field[[]SessionChatParamsPartUnion] `json:"parts,required"` - ProviderID param.Field[string] `json:"providerID,required"` - Directory param.Field[string] `query:"directory"` - Agent param.Field[string] `json:"agent"` - MessageID param.Field[string] `json:"messageID"` - System param.Field[string] `json:"system"` - Tools param.Field[map[string]bool] `json:"tools"` -} - -func (r SessionChatParams) MarshalJSON() (data []byte, err error) { - return apijson.MarshalRoot(r) -} - -// URLQuery serializes [SessionChatParams]'s query parameters as `url.Values`. -func (r SessionChatParams) URLQuery() (v url.Values) { - return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ - ArrayFormat: apiquery.ArrayQueryFormatComma, - NestedFormat: apiquery.NestedQueryFormatBrackets, - }) -} - -type SessionChatParamsPart struct { - 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"` - Name param.Field[string] `json:"name"` - Source param.Field[interface{}] `json:"source"` - Synthetic param.Field[bool] `json:"synthetic"` - Text param.Field[string] `json:"text"` - Time param.Field[interface{}] `json:"time"` - URL param.Field[string] `json:"url"` -} - -func (r SessionChatParamsPart) MarshalJSON() (data []byte, err error) { - return apijson.MarshalRoot(r) -} - -func (r SessionChatParamsPart) implementsSessionChatParamsPartUnion() {} - -// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam], -// [SessionChatParamsPart]. -type SessionChatParamsPartUnion interface { - implementsSessionChatParamsPartUnion() -} - -type SessionChatParamsPartsType string - -const ( - SessionChatParamsPartsTypeText SessionChatParamsPartsType = "text" - SessionChatParamsPartsTypeFile SessionChatParamsPartsType = "file" - SessionChatParamsPartsTypeAgent SessionChatParamsPartsType = "agent" -) - -func (r SessionChatParamsPartsType) IsKnown() bool { - switch r { - case SessionChatParamsPartsTypeText, SessionChatParamsPartsTypeFile, SessionChatParamsPartsTypeAgent: - return true - } - return false -} - type SessionChildrenParams struct { Directory param.Field[string] `query:"directory"` } @@ -2611,6 +2547,78 @@ func (r SessionMessagesParams) URLQuery() (v url.Values) { }) } +type SessionPromptParams struct { + Parts param.Field[[]SessionPromptParamsPartUnion] `json:"parts,required"` + Directory param.Field[string] `query:"directory"` + Agent param.Field[string] `json:"agent"` + MessageID param.Field[string] `json:"messageID"` + Model param.Field[SessionPromptParamsModel] `json:"model"` + System param.Field[string] `json:"system"` + Tools param.Field[map[string]bool] `json:"tools"` +} + +func (r SessionPromptParams) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +// URLQuery serializes [SessionPromptParams]'s query parameters as `url.Values`. +func (r SessionPromptParams) URLQuery() (v url.Values) { + return apiquery.MarshalWithSettings(r, apiquery.QuerySettings{ + ArrayFormat: apiquery.ArrayQueryFormatComma, + NestedFormat: apiquery.NestedQueryFormatBrackets, + }) +} + +type SessionPromptParamsPart struct { + Type param.Field[SessionPromptParamsPartsType] `json:"type,required"` + ID param.Field[string] `json:"id"` + Filename param.Field[string] `json:"filename"` + Mime param.Field[string] `json:"mime"` + Name param.Field[string] `json:"name"` + Source param.Field[interface{}] `json:"source"` + Synthetic param.Field[bool] `json:"synthetic"` + Text param.Field[string] `json:"text"` + Time param.Field[interface{}] `json:"time"` + URL param.Field[string] `json:"url"` +} + +func (r SessionPromptParamsPart) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + +func (r SessionPromptParamsPart) implementsSessionPromptParamsPartUnion() {} + +// Satisfied by [TextPartInputParam], [FilePartInputParam], [AgentPartInputParam], +// [SessionPromptParamsPart]. +type SessionPromptParamsPartUnion interface { + implementsSessionPromptParamsPartUnion() +} + +type SessionPromptParamsPartsType string + +const ( + SessionPromptParamsPartsTypeText SessionPromptParamsPartsType = "text" + SessionPromptParamsPartsTypeFile SessionPromptParamsPartsType = "file" + SessionPromptParamsPartsTypeAgent SessionPromptParamsPartsType = "agent" +) + +func (r SessionPromptParamsPartsType) IsKnown() bool { + switch r { + case SessionPromptParamsPartsTypeText, SessionPromptParamsPartsTypeFile, SessionPromptParamsPartsTypeAgent: + return true + } + return false +} + +type SessionPromptParamsModel struct { + ModelID param.Field[string] `json:"modelID,required"` + ProviderID param.Field[string] `json:"providerID,required"` +} + +func (r SessionPromptParamsModel) MarshalJSON() (data []byte, err error) { + return apijson.MarshalRoot(r) +} + type SessionRevertParams struct { MessageID param.Field[string] `json:"messageID,required"` Directory param.Field[string] `query:"directory"` diff --git a/packages/sdk/go/session_test.go b/packages/sdk/go/session_test.go index 13a7315b4..f4cbc04b1 100644 --- a/packages/sdk/go/session_test.go +++ b/packages/sdk/go/session_test.go @@ -148,52 +148,6 @@ func TestSessionAbortWithOptionalParams(t *testing.T) { } } -func TestSessionChatWithOptionalParams(t *testing.T) { - t.Skip("Prism tests are disabled") - 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.Chat( - context.TODO(), - "id", - opencode.SessionChatParams{ - ModelID: opencode.F("modelID"), - Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartInputParam{ - Text: opencode.F("text"), - Type: opencode.F(opencode.TextPartInputTypeText), - ID: opencode.F("id"), - Synthetic: opencode.F(true), - Time: opencode.F(opencode.TextPartInputTimeParam{ - Start: opencode.F(0.000000), - End: opencode.F(0.000000), - }), - }}), - ProviderID: opencode.F("providerID"), - Directory: opencode.F("directory"), - Agent: opencode.F("agent"), - MessageID: opencode.F("msg"), - System: opencode.F("system"), - Tools: opencode.F(map[string]bool{ - "foo": true, - }), - }, - ) - 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 TestSessionChildrenWithOptionalParams(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" @@ -371,6 +325,54 @@ func TestSessionMessagesWithOptionalParams(t *testing.T) { } } +func TestSessionPromptWithOptionalParams(t *testing.T) { + t.Skip("Prism tests are disabled") + 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.Prompt( + context.TODO(), + "id", + opencode.SessionPromptParams{ + Parts: opencode.F([]opencode.SessionPromptParamsPartUnion{opencode.TextPartInputParam{ + Text: opencode.F("text"), + Type: opencode.F(opencode.TextPartInputTypeText), + ID: opencode.F("id"), + Synthetic: opencode.F(true), + Time: opencode.F(opencode.TextPartInputTimeParam{ + Start: opencode.F(0.000000), + End: opencode.F(0.000000), + }), + }}), + Directory: opencode.F("directory"), + Agent: opencode.F("agent"), + MessageID: opencode.F("msg"), + Model: opencode.F(opencode.SessionPromptParamsModel{ + ModelID: opencode.F("modelID"), + ProviderID: opencode.F("providerID"), + }), + System: opencode.F("system"), + Tools: opencode.F(map[string]bool{ + "foo": true, + }), + }, + ) + 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 TestSessionRevertWithOptionalParams(t *testing.T) { t.Skip("Prism tests are disabled") baseURL := "http://localhost:4010" diff --git a/packages/sdk/go/shared/union.go b/packages/sdk/go/shared/union.go new file mode 100644 index 000000000..91c73305d --- /dev/null +++ b/packages/sdk/go/shared/union.go @@ -0,0 +1,11 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +package shared + +type UnionBool bool + +func (UnionBool) ImplementsConfigProviderOptionsTimeoutUnion() {} + +type UnionInt int64 + +func (UnionInt) ImplementsConfigProviderOptionsTimeoutUnion() {} diff --git a/packages/sdk/js/src/gen/sdk.gen.ts b/packages/sdk/js/src/gen/sdk.gen.ts index 4c1bfdbde..23fc3e371 100644 --- a/packages/sdk/js/src/gen/sdk.gen.ts +++ b/packages/sdk/js/src/gen/sdk.gen.ts @@ -4,6 +4,8 @@ import type { Options as ClientOptions, TDataShape, Client } from "./client/inde import type { ProjectListData, ProjectListResponses, + ButtCurrentData, + ButtCurrentResponses, EventSubscribeData, EventSubscribeResponses, ConfigGetData, @@ -35,8 +37,8 @@ import type { SessionSummarizeResponses, SessionMessagesData, SessionMessagesResponses, - SessionChatData, - SessionChatResponses, + SessionPromptData, + SessionPromptResponses, SessionMessageData, SessionMessageResponses, SessionCommandData, @@ -132,6 +134,18 @@ class Project extends _HeyApiClient { } } +class Butt extends _HeyApiClient { + /** + * Get the current project + */ + public current(options?: Options) { + return (options?.client ?? this._client).get({ + url: "/project/current", + ...options, + }) + } +} + class Event extends _HeyApiClient { /** * Get events @@ -318,8 +332,8 @@ class Session extends _HeyApiClient { /** * Create and send a new message to a session */ - public chat(options: Options) { - return (options.client ?? this._client).post({ + public prompt(options: Options) { + return (options.client ?? this._client).post({ url: "/session/{id}/message", ...options, headers: { @@ -635,6 +649,7 @@ export class OpencodeClient extends _HeyApiClient { }) } project = new Project({ client: this._client }) + butt = new Butt({ client: this._client }) event = new Event({ client: this._client }) config = new Config({ client: this._client }) path = new Path({ client: this._client }) diff --git a/packages/sdk/js/src/gen/types.gen.ts b/packages/sdk/js/src/gen/types.gen.ts index 194b69fc6..5084380de 100644 --- a/packages/sdk/js/src/gen/types.gen.ts +++ b/packages/sdk/js/src/gen/types.gen.ts @@ -645,7 +645,11 @@ export type Config = { options?: { apiKey?: string baseURL?: string - [key: string]: unknown | string | undefined + /** + * Timeout in milliseconds for requests to this provider. Default is 300000 (5 minutes). Set to false to disable timeout. + */ + timeout?: number | false + [key: string]: unknown | string | (number | false) | undefined } } } @@ -1052,6 +1056,8 @@ export type LayoutConfig = "auto" | "stretch" export type Path = { state: string config: string + worktree: string + directory: string } export type _Error = { @@ -1196,6 +1202,24 @@ export type ProjectListResponses = { export type ProjectListResponse = ProjectListResponses[keyof ProjectListResponses] +export type ButtCurrentData = { + body?: never + path?: never + query?: { + directory?: string + } + url: "/project/current" +} + +export type ButtCurrentResponses = { + /** + * Current project + */ + 200: Project +} + +export type ButtCurrentResponse = ButtCurrentResponses[keyof ButtCurrentResponses] + export type EventSubscribeData = { body?: never path?: never @@ -1519,11 +1543,13 @@ export type SessionMessagesResponses = { export type SessionMessagesResponse = SessionMessagesResponses[keyof SessionMessagesResponses] -export type SessionChatData = { +export type SessionPromptData = { body?: { messageID?: string - providerID: string - modelID: string + model?: { + providerID: string + modelID: string + } agent?: string system?: string tools?: { @@ -1553,7 +1579,7 @@ export type SessionChatData = { url: "/session/{id}/message" } -export type SessionChatResponses = { +export type SessionPromptResponses = { /** * Created message */ @@ -1563,7 +1589,7 @@ export type SessionChatResponses = { } } -export type SessionChatResponse = SessionChatResponses[keyof SessionChatResponses] +export type SessionPromptResponse = SessionPromptResponses[keyof SessionPromptResponses] export type SessionMessageData = { body?: never diff --git a/packages/sdk/stainless/stainless.yml b/packages/sdk/stainless/stainless.yml index ac6da0468..1efe263a0 100644 --- a/packages/sdk/stainless/stainless.yml +++ b/packages/sdk/stainless/stainless.yml @@ -147,7 +147,7 @@ resources: summarize: post /session/{id}/summarize message: get /session/{id}/message/{messageID} messages: get /session/{id}/message - chat: post /session/{id}/message + prompt: post /session/{id}/message command: post /session/{id}/command shell: post /session/{id}/shell update: patch /session/{id} diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go index 0733719c9..4b333a93c 100644 --- a/packages/tui/internal/app/app.go +++ b/packages/tui/internal/app/app.go @@ -786,12 +786,14 @@ func (a *App) SendPrompt(ctx context.Context, prompt Prompt) (*App, tea.Cmd) { a.Messages = append(a.Messages, message) cmds = append(cmds, func() tea.Msg { - _, err := a.Client.Session.Chat(ctx, a.Session.ID, opencode.SessionChatParams{ - ProviderID: opencode.F(a.Provider.ID), - ModelID: opencode.F(a.Model.ID), - Agent: opencode.F(a.Agent().Name), - MessageID: opencode.F(messageID), - Parts: opencode.F(message.ToSessionChatParams()), + _, err := a.Client.Session.Prompt(ctx, a.Session.ID, opencode.SessionPromptParams{ + Model: opencode.F(opencode.SessionPromptParamsModel{ + ProviderID: opencode.F(a.Provider.ID), + ModelID: opencode.F(a.Model.ID), + }), + Agent: opencode.F(a.Agent().Name), + MessageID: opencode.F(messageID), + Parts: opencode.F(message.ToSessionChatParams()), }) if err != nil { errormsg := fmt.Sprintf("failed to send message: %v", err) diff --git a/packages/tui/internal/app/prompt.go b/packages/tui/internal/app/prompt.go index 81c4b6a7d..8701a2072 100644 --- a/packages/tui/internal/app/prompt.go +++ b/packages/tui/internal/app/prompt.go @@ -204,8 +204,8 @@ func (m Message) ToPrompt() (*Prompt, error) { return nil, errors.New("unknown message type") } -func (m Message) ToSessionChatParams() []opencode.SessionChatParamsPartUnion { - parts := []opencode.SessionChatParamsPartUnion{} +func (m Message) ToSessionChatParams() []opencode.SessionPromptParamsPartUnion { + parts := []opencode.SessionPromptParamsPartUnion{} for _, part := range m.Parts { switch p := part.(type) { case opencode.TextPart: