This commit is contained in:
Dax Raad 2025-07-05 15:29:16 -04:00
parent 6eace8c34f
commit f0dcebcc21
10 changed files with 603 additions and 600 deletions

View file

@ -1,9 +1,9 @@
import type { LanguageModelV1Prompt } from "ai"
import type { ModelMessage } from "ai"
import { unique } from "remeda"
export namespace ProviderTransform {
export function message(
msgs: LanguageModelV1Prompt,
msgs: ModelMessage[],
providerID: string,
modelID: string,
) {
@ -12,8 +12,8 @@ export namespace ProviderTransform {
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
for (const msg of unique([...system, ...final])) {
msg.providerMetadata = {
...msg.providerMetadata,
msg.providerOptions = {
...msg.providerOptions,
anthropic: {
cacheControl: { type: "ephemeral" },
},
@ -28,8 +28,8 @@ export namespace ProviderTransform {
const final = msgs.filter((msg) => msg.role !== "system").slice(-2)
for (const msg of unique([...system, ...final])) {
msg.providerMetadata = {
...msg.providerMetadata,
msg.providerOptions = {
...msg.providerOptions,
bedrock: {
cachePoint: { type: "ephemeral" },
},

View file

@ -494,24 +494,23 @@ export namespace Session {
description: item.description,
inputSchema: item.parameters as ZodSchema,
async execute(args, opts) {
const start = Date.now()
try {
const result = await item.execute(args, {
sessionID: input.sessionID,
abort: abort.signal,
messageID: next.id,
metadata: async (val) => {
const match = next.parts.find(
(p): p is MessageV2.ToolPart =>
p.type === "tool" && p.id === opts.toolCallId,
)
await updateMessage(next)
},
})
return result
} catch (e: any) {
return e.toString()
}
const result = await item.execute(args, {
sessionID: input.sessionID,
abort: abort.signal,
messageID: next.id,
metadata: async (val) => {
const match = next.parts.find(
(p): p is MessageV2.ToolPart =>
p.type === "tool" && p.id === opts.toolCallId,
)
if (match && match.state.status === "running") {
match.state.title = val.title
match.state.metadata = val.metadata
}
await updateMessage(next)
},
})
return result
},
toModelOutput(result) {
return {
@ -526,7 +525,6 @@ export namespace Session {
const execute = item.execute
if (!execute) continue
item.execute = async (args, opts) => {
const start = Date.now()
try {
const result = await execute(args, opts)
return result.content
@ -612,6 +610,9 @@ export namespace Session {
})
break
case "tool-input-delta":
break
case "tool-call": {
const match = next.parts.find(
(p): p is MessageV2.ToolPart =>
@ -659,6 +660,30 @@ export namespace Session {
break
}
case "tool-error": {
const match = next.parts.find(
(p): p is MessageV2.ToolPart =>
p.type === "tool" && p.id === value.toolCallId,
)
if (match && match.state.status === "running") {
match.state = {
status: "error",
input: value.input,
error: (value.error as any).toString(),
time: {
start: match.state.time.start,
end: Date.now(),
},
}
Bus.publish(MessageV2.Event.PartUpdated, {
part: match,
sessionID: next.sessionID,
messageID: next.id,
})
}
break
}
case "error":
const e = value.error
log.error("", {

View file

@ -25,6 +25,8 @@ export namespace MessageV2 {
.object({
status: z.literal("running"),
input: z.any(),
title: z.string().optional(),
metadata: z.record(z.any()).optional(),
time: z.object({
start: z.number(),
}),
@ -33,7 +35,7 @@ export namespace MessageV2 {
ref: "ToolStateRunning",
})
export const ToolInvocationCompleted = z
export const ToolStateCompleted = z
.object({
status: z.literal("completed"),
input: z.any(),
@ -46,14 +48,29 @@ export namespace MessageV2 {
}),
})
.openapi({
ref: "ToolInvocationCompleted",
ref: "ToolStateCompleted",
})
export const ToolStateError = z
.object({
status: z.literal("error"),
input: z.any(),
error: z.string(),
time: z.object({
start: z.number(),
end: z.number(),
}),
})
.openapi({
ref: "ToolStateError",
})
export const ToolState = z
.discriminatedUnion("status", [
ToolStatePending,
ToolStateRunning,
ToolInvocationCompleted,
ToolStateCompleted,
ToolStateError,
])
.openapi({
ref: "ToolState",
@ -348,7 +365,7 @@ export namespace MessageV2 {
text: part.text,
},
]
if (part.type === "tool")
if (part.type === "tool") {
if (part.state.status === "completed")
return [
{
@ -367,6 +384,25 @@ export namespace MessageV2 {
},
},
]
if (part.state.status === "error")
return [
{
type: "tool-call",
input: part.state.input,
toolName: part.tool,
toolCallId: part.id,
},
{
type: "tool-result",
toolCallId: part.id,
toolName: part.tool,
output: {
type: "text",
value: part.state.error,
},
},
]
}
return []
},

View file

@ -15,7 +15,7 @@ require (
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.16.0
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3
github.com/sst/opencode-sdk-go v0.1.0-alpha.8
github.com/stainless-sdks/opencode-go dev
github.com/tidwall/gjson v1.14.4
rsc.io/qr v0.2.0
)

View file

@ -1,4 +1,4 @@
configured_endpoints: 20
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-9dcfe3e5f2d5eef1bcbf70cb6bd38e71802f1197468d49b03d4148e741726553.yml
openapi_spec_hash: 65be139c91c4241a665b458cbfb08f8e
config_hash: 61d6c861cb32b16df2ba4a4973c339ee
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/opencode%2Fopencode-15eeb028f79b9a065b4e54a6ea6a58631e9bd5004f97820f0c79d18e3f8bac84.yml
openapi_spec_hash: 38c8bacb6c8e4c46852a3e81e3fb9fda
config_hash: 348a85e725de595ca05a61f4333794ac

View file

@ -72,15 +72,23 @@ 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#UserMessagePartUnionParam">UserMessagePartUnionParam</a>
Response 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#AssistantMessage">AssistantMessage</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#AssistantMessagePart">AssistantMessagePart</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#FilePart">FilePart</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#Message">Message</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#Session">Session</a>
- <a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go">opencode</a>.<a href="https://pkg.go.dev/github.com/sst/opencode-sdk-go#StepStartPart">StepStartPart</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#TextPart">TextPart</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#ToolPart">ToolPart</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#ToolStateCompleted">ToolStateCompleted</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#ToolStateError">ToolStateError</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#ToolStatePending">ToolStatePending</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#ToolStateRunning">ToolStateRunning</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#UserMessagePart">UserMessagePart</a>
Methods:

View file

@ -673,7 +673,7 @@ func (r EventListResponseEventMessagePartUpdated) implementsEventListResponse()
type EventListResponseEventMessagePartUpdatedProperties struct {
MessageID string `json:"messageID,required"`
Part EventListResponseEventMessagePartUpdatedPropertiesPart `json:"part,required"`
Part AssistantMessagePart `json:"part,required"`
SessionID string `json:"sessionID,required"`
JSON eventListResponseEventMessagePartUpdatedPropertiesJSON `json:"-"`
}
@ -696,134 +696,6 @@ func (r eventListResponseEventMessagePartUpdatedPropertiesJSON) RawJSON() string
return r.raw
}
type EventListResponseEventMessagePartUpdatedPropertiesPart struct {
Type EventListResponseEventMessagePartUpdatedPropertiesPartType `json:"type,required"`
ID string `json:"id"`
// This field can have the runtime type of [ToolPartState].
State interface{} `json:"state"`
Text string `json:"text"`
Tool string `json:"tool"`
JSON eventListResponseEventMessagePartUpdatedPropertiesPartJSON `json:"-"`
union EventListResponseEventMessagePartUpdatedPropertiesPartUnion
}
// eventListResponseEventMessagePartUpdatedPropertiesPartJSON contains the JSON
// metadata for the struct [EventListResponseEventMessagePartUpdatedPropertiesPart]
type eventListResponseEventMessagePartUpdatedPropertiesPartJSON struct {
Type apijson.Field
ID apijson.Field
State apijson.Field
Text apijson.Field
Tool apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r eventListResponseEventMessagePartUpdatedPropertiesPartJSON) RawJSON() string {
return r.raw
}
func (r *EventListResponseEventMessagePartUpdatedPropertiesPart) UnmarshalJSON(data []byte) (err error) {
*r = EventListResponseEventMessagePartUpdatedPropertiesPart{}
err = apijson.UnmarshalRoot(data, &r.union)
if err != nil {
return err
}
return apijson.Port(r.union, &r)
}
// AsUnion returns a [EventListResponseEventMessagePartUpdatedPropertiesPartUnion]
// interface which you can cast to the specific types for more type safety.
//
// Possible runtime types of the union are [TextPart], [ToolPart],
// [EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart].
func (r EventListResponseEventMessagePartUpdatedPropertiesPart) AsUnion() EventListResponseEventMessagePartUpdatedPropertiesPartUnion {
return r.union
}
// Union satisfied by [TextPart], [ToolPart] or
// [EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart].
type EventListResponseEventMessagePartUpdatedPropertiesPartUnion interface {
implementsEventListResponseEventMessagePartUpdatedPropertiesPart()
}
func init() {
apijson.RegisterUnion(
reflect.TypeOf((*EventListResponseEventMessagePartUpdatedPropertiesPartUnion)(nil)).Elem(),
"type",
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(TextPart{}),
DiscriminatorValue: "text",
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(ToolPart{}),
DiscriminatorValue: "tool",
},
apijson.UnionVariant{
TypeFilter: gjson.JSON,
Type: reflect.TypeOf(EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart{}),
DiscriminatorValue: "step-start",
},
)
}
type EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart struct {
Type EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartType `json:"type,required"`
JSON eventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartJSON `json:"-"`
}
// eventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartJSON contains
// the JSON metadata for the struct
// [EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart]
type eventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartJSON struct {
Type apijson.Field
raw string
ExtraFields map[string]apijson.Field
}
func (r *EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart) UnmarshalJSON(data []byte) (err error) {
return apijson.UnmarshalRoot(data, r)
}
func (r eventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartJSON) RawJSON() string {
return r.raw
}
func (r EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPart) implementsEventListResponseEventMessagePartUpdatedPropertiesPart() {
}
type EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartType string
const (
EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartTypeStepStart EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartType = "step-start"
)
func (r EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartType) IsKnown() bool {
switch r {
case EventListResponseEventMessagePartUpdatedPropertiesPartStepStartPartTypeStepStart:
return true
}
return false
}
type EventListResponseEventMessagePartUpdatedPropertiesPartType string
const (
EventListResponseEventMessagePartUpdatedPropertiesPartTypeText EventListResponseEventMessagePartUpdatedPropertiesPartType = "text"
EventListResponseEventMessagePartUpdatedPropertiesPartTypeTool EventListResponseEventMessagePartUpdatedPropertiesPartType = "tool"
EventListResponseEventMessagePartUpdatedPropertiesPartTypeStepStart EventListResponseEventMessagePartUpdatedPropertiesPartType = "step-start"
)
func (r EventListResponseEventMessagePartUpdatedPropertiesPartType) IsKnown() bool {
switch r {
case EventListResponseEventMessagePartUpdatedPropertiesPartTypeText, EventListResponseEventMessagePartUpdatedPropertiesPartTypeTool, EventListResponseEventMessagePartUpdatedPropertiesPartTypeStepStart:
return true
}
return false
}
type EventListResponseEventMessagePartUpdatedType string
const (

File diff suppressed because it is too large Load diff

View file

@ -118,7 +118,7 @@ func TestSessionChat(t *testing.T) {
"id",
opencode.SessionChatParams{
ModelID: opencode.F("modelID"),
Parts: opencode.F([]opencode.SessionChatParamsPartUnion{opencode.TextPartParam{
Parts: opencode.F([]opencode.UserMessagePartUnionParam{opencode.TextPartParam{
Text: opencode.F("text"),
Type: opencode.F(opencode.TextPartTypeText),
}}),

View file

@ -81,7 +81,15 @@ resources:
textPart: TextPart
filePart: FilePart
toolPart: ToolPart
stepStartPart: StepStartPart
assistantMessage: AssistantMessage
userMessagePart: UserMessagePart
assistantMessagePart: AssistantMessagePart
toolStatePending: ToolStatePending
toolStateRunning: ToolStateRunning
toolStateCompleted: ToolStateCompleted
toolStateError: ToolStateError
methods:
list: get /session
create: post /session