|
@@ -14,6 +14,7 @@ import {
|
|
|
type ProviderMetadata,
|
|
type ProviderMetadata,
|
|
|
type ToolCallPart,
|
|
type ToolCallPart,
|
|
|
type ToolDefinition,
|
|
type ToolDefinition,
|
|
|
|
|
+ type ToolResultContentPart,
|
|
|
type ToolResultPart,
|
|
type ToolResultPart,
|
|
|
} from "../schema"
|
|
} from "../schema"
|
|
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
|
import { JsonObject, optionalArray, optionalNull, ProviderShared } from "./shared"
|
|
@@ -96,10 +97,18 @@ const AnthropicServerToolResultBlock = Schema.Struct({
|
|
|
})
|
|
})
|
|
|
type AnthropicServerToolResultBlock = Schema.Schema.Type<typeof AnthropicServerToolResultBlock>
|
|
type AnthropicServerToolResultBlock = Schema.Schema.Type<typeof AnthropicServerToolResultBlock>
|
|
|
|
|
|
|
|
|
|
+// Anthropic accepts either a plain string or an ordered array of text/image
|
|
|
|
|
+// blocks inside `tool_result.content`. The array form is required when a tool
|
|
|
|
|
+// returns image bytes (screenshot, image search, etc.) so they can be passed
|
|
|
|
|
+// to the model as proper image inputs instead of being JSON-stringified into
|
|
|
|
|
+// the prompt — which silently inflates context by megabytes and can push the
|
|
|
|
|
+// conversation over the model's token limit.
|
|
|
|
|
+const AnthropicToolResultContent = Schema.Union([AnthropicTextBlock, AnthropicImageBlock])
|
|
|
|
|
+
|
|
|
const AnthropicToolResultBlock = Schema.Struct({
|
|
const AnthropicToolResultBlock = Schema.Struct({
|
|
|
type: Schema.tag("tool_result"),
|
|
type: Schema.tag("tool_result"),
|
|
|
tool_use_id: Schema.String,
|
|
tool_use_id: Schema.String,
|
|
|
- content: Schema.String,
|
|
|
|
|
|
|
+ content: Schema.Union([Schema.String, Schema.Array(AnthropicToolResultContent)]),
|
|
|
is_error: Schema.optional(Schema.Boolean),
|
|
is_error: Schema.optional(Schema.Boolean),
|
|
|
cache_control: Schema.optional(AnthropicCacheControl),
|
|
cache_control: Schema.optional(AnthropicCacheControl),
|
|
|
})
|
|
})
|
|
@@ -298,6 +307,31 @@ const lowerImage = Effect.fn("AnthropicMessages.lowerImage")(function* (part: Me
|
|
|
} satisfies AnthropicImageBlock
|
|
} satisfies AnthropicImageBlock
|
|
|
})
|
|
})
|
|
|
|
|
|
|
|
|
|
+// Tool results may carry structured text/images. Keep media as provider-native
|
|
|
|
|
+// content instead of JSON-stringifying base64 into a prompt string.
|
|
|
|
|
+const lowerToolResultContentItem = Effect.fn("AnthropicMessages.lowerToolResultContentItem")(function* (
|
|
|
|
|
+ item: ToolResultContentPart,
|
|
|
|
|
+) {
|
|
|
|
|
+ if (item.type === "text") return { type: "text" as const, text: item.text } satisfies AnthropicTextBlock
|
|
|
|
|
+ if (item.mediaType.startsWith("image/"))
|
|
|
|
|
+ return {
|
|
|
|
|
+ type: "image" as const,
|
|
|
|
|
+ source: {
|
|
|
|
|
+ type: "base64" as const,
|
|
|
|
|
+ media_type: item.mediaType,
|
|
|
|
|
+ data: ProviderShared.mediaBase64(item),
|
|
|
|
|
+ },
|
|
|
|
|
+ } satisfies AnthropicImageBlock
|
|
|
|
|
+ return yield* invalid(`Anthropic Messages tool-result media content only supports images, got ${item.mediaType}`)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
|
|
+const lowerToolResultContent = Effect.fn("AnthropicMessages.lowerToolResultContent")(function* (part: ToolResultPart) {
|
|
|
|
|
+ // Text / json / error results stay as a string for backward compatibility
|
|
|
|
|
+ // with existing cassettes and provider expectations.
|
|
|
|
|
+ if (part.result.type !== "content") return ProviderShared.toolResultText(part)
|
|
|
|
|
+ return yield* Effect.forEach(part.result.value, lowerToolResultContentItem)
|
|
|
|
|
+})
|
|
|
|
|
+
|
|
|
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
|
const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
|
|
request: LLMRequest,
|
|
request: LLMRequest,
|
|
|
breakpoints: Cache.Breakpoints,
|
|
breakpoints: Cache.Breakpoints,
|
|
@@ -360,7 +394,7 @@ const lowerMessages = Effect.fn("AnthropicMessages.lowerMessages")(function* (
|
|
|
content.push({
|
|
content.push({
|
|
|
type: "tool_result",
|
|
type: "tool_result",
|
|
|
tool_use_id: part.id,
|
|
tool_use_id: part.id,
|
|
|
- content: ProviderShared.toolResultText(part),
|
|
|
|
|
|
|
+ content: yield* lowerToolResultContent(part),
|
|
|
is_error: part.result.type === "error" ? true : undefined,
|
|
is_error: part.result.type === "error" ? true : undefined,
|
|
|
cache_control: cacheControl(breakpoints, part.cache),
|
|
cache_control: cacheControl(breakpoints, part.cache),
|
|
|
})
|
|
})
|