Part is deliberately closed — it has an unexported marker method, so only skyl
can implement it. That constraint turns a class of runtime bug into a compile
error.
Reference#
The four implementations:
| Type | Carries |
|---|---|
| Text | A run of plain text |
| Image | An image, as inline bytes or a URL |
| ToolCall | The model's request to invoke a tool |
| ToolResult | The outcome of a tool invocation |
Caveats
- You cannot implement it. The marker method is unexported.
- An adapter that cannot represent a part returns
ErrUnsupportednaming the part, before sending anything — rather than dropping it silently. - Support varies by part and by the role of the message carrying it.
- Because it is an interface,
Messagedoes not round-trip throughencoding/json. Persist your own shape.
Why closed#
Deep diveA compile error beats a production surprise
An open interface would let you construct a part no adapter knows how to render.
Every adapter's type switch would hit its default and have to either drop it
silently or fail at runtime — a bug that appears in production, on one vendor
and not another, long after the code was written.
Closed means the compiler rejects it at the point you type it. The cost is that
adding a part type requires a change to skyl. The benefit is that every part you
can construct is one every adapter has a defined answer for, even if that
answer is a clean ErrUnsupported.
If you need something skyl does not model, ProviderOptions is the hatch.
What each adapter accepts#
| Part (role) | anthropic | openai / compat | gemini |
|---|---|---|---|
| Text (user, assistant) | mapped | mapped | mapped |
| Text (tool role) | becomes user content | rejected | becomes user content |
| Image URL (user) | mapped | mapped | rejected |
| Image data (user) | mapped | mapped | mapped |
| Image (assistant) | accepted | rejected | accepted |
| ToolCall (assistant) | mapped | mapped | mapped |
| ToolCall (user) | accepted | rejected | accepted |
| ToolResult (tool role) | mapped | mapped | mapped |
Usage#
Switching over parts
for _, part := range resp.Message.Parts {
switch p := part.(type) {
case skyl.Text:
fmt.Print(p.Text)
case skyl.ToolCall:
fmt.Printf("[calling %s]", p.Name)
case skyl.Image:
fmt.Print("[image]")
case skyl.ToolResult:
fmt.Printf("[result for %s]", p.CallID)
}
}for _, part := range resp.Message.Parts {
switch p := part.(type) {
case skyl.Text:
fmt.Print(p.Text)
case skyl.ToolCall:
fmt.Printf("[calling %s]", p.Name)
case skyl.Image:
fmt.Print("[image]")
case skyl.ToolResult:
fmt.Printf("[result for %s]", p.CallID)
}
}Because the interface is closed, this switch is exhaustive by construction.
Handling an unsupported part
_, err := client.Complete(ctx, req)
if errors.Is(err, skyl.ErrUnsupported) {
// e.g. "gemini: Gemini requires inline image data, not a URL"
var e *skyl.Error
if errors.As(err, &e) {
return fmt.Errorf("this model (%s) cannot handle that input: %s", e.Provider, e.Message)
}
}_, err := client.Complete(ctx, req)
if errors.Is(err, skyl.ErrUnsupported) {
// e.g. "gemini: Gemini requires inline image data, not a URL"
var e *skyl.Error
if errors.As(err, &e) {
return fmt.Errorf("this model (%s) cannot handle that input: %s", e.Provider, e.Message)
}
}Troubleshooting#
I want to add an audio part
You cannot — the interface is closed. Send it through
ProviderOptions, which is the hatch for anything
skyl does not model. If several vendors support it stably, that is a good issue
to open.
ErrUnsupported naming a part
The adapter cannot represent it. The message says exactly which part and why. This is deliberate: a silently dropped image looks like a model that ignored your question.