mirror of
https://wget.la/https://github.com/leookun/cursor-byok
synced 2026-08-17 19:47:10 +08:00
Support read image
This commit is contained in:
+159
-156
@@ -1,156 +1,159 @@
|
|||||||
version: "3"
|
version: "3"
|
||||||
|
|
||||||
includes:
|
includes:
|
||||||
common: ../Taskfile.yml
|
common: ../Taskfile.yml
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
build:
|
build:
|
||||||
summary: 构建 Windows 程序
|
summary: 构建 Windows 程序
|
||||||
internal: true
|
internal: true
|
||||||
cmds:
|
cmds:
|
||||||
- task: build:native
|
- task: build:native
|
||||||
vars:
|
vars:
|
||||||
ARCH:
|
ARCH:
|
||||||
ref: .ARCH
|
ref: .ARCH
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
ref: .OUTPUT
|
ref: .OUTPUT
|
||||||
BUILD_FLAGS:
|
BUILD_FLAGS:
|
||||||
ref: .BUILD_FLAGS
|
ref: .BUILD_FLAGS
|
||||||
EXTRA_TAGS:
|
EXTRA_TAGS:
|
||||||
ref: .EXTRA_TAGS
|
ref: .EXTRA_TAGS
|
||||||
DEV:
|
DEV:
|
||||||
ref: .DEV
|
ref: .DEV
|
||||||
SCAN:
|
SCAN:
|
||||||
ref: .SCAN
|
ref: .SCAN
|
||||||
|
|
||||||
build:native:
|
build:native:
|
||||||
summary: 使用 Go 原生交叉编译构建 Windows 程序
|
summary: 使用 Go 原生交叉编译构建 Windows 程序
|
||||||
internal: true
|
internal: true
|
||||||
deps:
|
deps:
|
||||||
- task: common:go:mod:tidy
|
- task: common:go:mod:tidy
|
||||||
- task: common:build:frontend
|
- task: common:build:frontend
|
||||||
vars:
|
vars:
|
||||||
BUILD_FLAGS:
|
BUILD_FLAGS:
|
||||||
ref: .BUILD_FLAGS
|
ref: .BUILD_FLAGS
|
||||||
DEV:
|
DEV:
|
||||||
ref: .DEV
|
ref: .DEV
|
||||||
SCAN:
|
SCAN:
|
||||||
ref: .SCAN
|
ref: .SCAN
|
||||||
- task: common:generate:icons
|
- task: common:generate:icons
|
||||||
cmds:
|
cmds:
|
||||||
- task: generate:syso
|
- task: generate:syso
|
||||||
vars:
|
vars:
|
||||||
ARCH:
|
ARCH:
|
||||||
ref: .ARCH
|
ref: .ARCH
|
||||||
- go build {{.BUILD_FLAGS}} -o "{{.OUTPUT}}"
|
- go build {{.BUILD_FLAGS}} -o "{{.OUTPUT}}"
|
||||||
- cmd: powershell Remove-item *.syso
|
- cmd: powershell Remove-item *.syso
|
||||||
platforms: [windows]
|
platforms: [windows]
|
||||||
- cmd: rm -f *.syso
|
- cmd: rm -f *.syso
|
||||||
platforms: [darwin, linux]
|
platforms: [darwin, linux]
|
||||||
vars:
|
vars:
|
||||||
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
||||||
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l" -ldflags="-X cursor/internal/buildinfo.Version={{.APP_VERSION}}"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui -X cursor/internal/buildinfo.Version={{.APP_VERSION}}"{{end}}'
|
BUILD_FLAGS: '{{if eq .DEV "true"}}{{if .EXTRA_TAGS}}-tags {{.EXTRA_TAGS}} {{end}}-buildvcs=false -gcflags=all="-l" -ldflags="-X cursor/internal/buildinfo.Version={{.APP_VERSION}}"{{else}}-tags production{{if .EXTRA_TAGS}},{{.EXTRA_TAGS}}{{end}} -trimpath -buildvcs=false -ldflags="-w -s -H windowsgui -X cursor/internal/buildinfo.Version={{.APP_VERSION}}"{{end}}'
|
||||||
env:
|
env:
|
||||||
GOOS: windows
|
GOOS: windows
|
||||||
CGO_ENABLED: 0
|
CGO_ENABLED: 0
|
||||||
GOARCH: '{{.ARCH | default ARCH}}'
|
GOARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
|
||||||
package:
|
package:
|
||||||
summary: 打包 Windows 安装程序
|
summary: 打包 Windows 安装程序
|
||||||
internal: true
|
internal: true
|
||||||
cmds:
|
cmds:
|
||||||
- task: '{{if eq (.FORMAT | default "nsis") "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}'
|
- task: '{{if eq (.FORMAT | default "nsis") "msix"}}create:msix:package{{else}}create:nsis:installer{{end}}'
|
||||||
vars:
|
vars:
|
||||||
FORMAT: '{{.FORMAT | default "nsis"}}'
|
FORMAT: '{{.FORMAT | default "nsis"}}'
|
||||||
|
|
||||||
generate:syso:
|
generate:syso:
|
||||||
summary: 生成 Windows 图标与版本信息资源
|
summary: 生成 Windows 图标与版本信息资源
|
||||||
internal: true
|
internal: true
|
||||||
dir: build
|
dir: build
|
||||||
cmds:
|
cmds:
|
||||||
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
- wails3 generate syso -arch {{.ARCH}} -icon windows/icon.ico -manifest windows/wails.exe.manifest -info windows/info.json -out ../wails_windows_{{.ARCH}}.syso
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
|
|
||||||
create:nsis:installer:
|
create:nsis:installer:
|
||||||
summary: 生成 NSIS 安装包
|
summary: 生成 NSIS 安装包
|
||||||
internal: true
|
internal: true
|
||||||
dir: build/windows/nsis
|
dir: build/windows/nsis
|
||||||
deps:
|
deps:
|
||||||
- task: build
|
- task: build
|
||||||
vars:
|
vars:
|
||||||
SCAN:
|
SCAN:
|
||||||
ref: .SCAN
|
ref: .SCAN
|
||||||
cmds:
|
cmds:
|
||||||
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
|
- wails3 generate webview2bootstrapper -dir "{{.ROOT_DIR}}/build/windows/nsis"
|
||||||
- |
|
- |
|
||||||
{{if eq OS "windows"}}
|
{{if eq OS "windows"}}
|
||||||
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi
|
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}\{{.BIN_DIR}}\{{.APP_NAME}}.exe" project.nsi
|
||||||
{{else}}
|
{{else}}
|
||||||
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
|
makensis -DARG_WAILS_{{.ARG_FLAG}}_BINARY="{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" project.nsi
|
||||||
{{end}}
|
{{end}}
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
ARG_FLAG: '{{if eq .ARCH "amd64"}}AMD64{{else}}ARM64{{end}}'
|
||||||
|
|
||||||
create:msix:package:
|
create:msix:package:
|
||||||
summary: 生成 MSIX 安装包
|
summary: 生成 MSIX 安装包
|
||||||
internal: true
|
internal: true
|
||||||
deps:
|
deps:
|
||||||
- task: build
|
- task: build
|
||||||
vars:
|
vars:
|
||||||
SCAN:
|
SCAN:
|
||||||
ref: .SCAN
|
ref: .SCAN
|
||||||
cmds:
|
cmds:
|
||||||
- |-
|
- |-
|
||||||
wails3 tool msix \
|
wails3 tool msix \
|
||||||
--config "{{.ROOT_DIR}}/wails.json" \
|
--config "{{.ROOT_DIR}}/wails.json" \
|
||||||
--name "{{.APP_NAME}}" \
|
--name "{{.APP_NAME}}" \
|
||||||
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
|
--executable "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}.exe" \
|
||||||
--arch "{{.ARCH}}" \
|
--arch "{{.ARCH}}" \
|
||||||
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
|
--out "{{.ROOT_DIR}}/{{.BIN_DIR}}/{{.APP_NAME}}-{{.ARCH}}.msix" \
|
||||||
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
|
{{if .CERT_PATH}}--cert "{{.CERT_PATH}}"{{end}} \
|
||||||
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
{{if .PUBLISHER}}--publisher "{{.PUBLISHER}}"{{end}} \
|
||||||
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
{{if .USE_MSIX_TOOL}}--use-msix-tool{{else}}--use-makeappx{{end}}
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
CERT_PATH: '{{.CERT_PATH | default ""}}'
|
||||||
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
PUBLISHER: '{{.PUBLISHER | default ""}}'
|
||||||
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
USE_MSIX_TOOL: '{{.USE_MSIX_TOOL | default "false"}}'
|
||||||
|
|
||||||
create:zip:
|
create:zip:
|
||||||
summary: 生成 Windows ZIP 包(包含 exe 与 certs)
|
summary: 生成 Windows ZIP 包(包含 exe)
|
||||||
internal: true
|
internal: true
|
||||||
vars:
|
vars:
|
||||||
ARCH: '{{.ARCH | default ARCH}}'
|
ARCH: '{{.ARCH | default ARCH}}'
|
||||||
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
||||||
ZIP_NAME: '{{.ZIP_NAME | default (printf "%s-windows-%s.zip" .APP_NAME .ARCH)}}'
|
ZIP_NAME: '{{.ZIP_NAME | default (printf "%s-windows-%s.zip" .APP_NAME .ARCH)}}'
|
||||||
STAGING_DIR: '{{.BIN_DIR}}/.zip-{{.ARCH}}'
|
STAGING_DIR: '{{.BIN_DIR}}/.zip-{{.ARCH}}'
|
||||||
cmds:
|
cmds:
|
||||||
- task: build
|
- task: build
|
||||||
vars:
|
vars:
|
||||||
ARCH:
|
ARCH:
|
||||||
ref: .ARCH
|
ref: .ARCH
|
||||||
OUTPUT:
|
OUTPUT:
|
||||||
ref: .OUTPUT
|
ref: .OUTPUT
|
||||||
BUILD_FLAGS:
|
BUILD_FLAGS:
|
||||||
ref: .BUILD_FLAGS
|
ref: .BUILD_FLAGS
|
||||||
EXTRA_TAGS:
|
EXTRA_TAGS:
|
||||||
ref: .EXTRA_TAGS
|
ref: .EXTRA_TAGS
|
||||||
DEV:
|
DEV:
|
||||||
ref: .DEV
|
ref: .DEV
|
||||||
SCAN:
|
SCAN:
|
||||||
ref: .SCAN
|
ref: .SCAN
|
||||||
- rm -rf "{{.STAGING_DIR}}"
|
- rm -rf "{{.STAGING_DIR}}"
|
||||||
- mkdir -p "{{.STAGING_DIR}}"
|
- mkdir -p "{{.STAGING_DIR}}"
|
||||||
- cp "{{.OUTPUT}}" "{{.STAGING_DIR}}/"
|
- cp "{{.OUTPUT}}" "{{.STAGING_DIR}}/"
|
||||||
- (cd "{{.STAGING_DIR}}" && zip -qry "../{{.ZIP_NAME}}" .)
|
- cmd: powershell -NoProfile -Command "Compress-Archive -Path '{{.STAGING_DIR}}/*' -DestinationPath '{{.BIN_DIR}}/{{.ZIP_NAME}}' -Force"
|
||||||
- rm -rf "{{.STAGING_DIR}}"
|
platforms: [windows]
|
||||||
- rm -f "{{.OUTPUT}}"
|
- cmd: (cd "{{.STAGING_DIR}}" && zip -qry "../{{.ZIP_NAME}}" .)
|
||||||
|
platforms: [darwin, linux]
|
||||||
run:
|
- rm -rf "{{.STAGING_DIR}}"
|
||||||
summary: 运行 Windows 可执行文件(仅在 Windows 可用)
|
- rm -f "{{.OUTPUT}}"
|
||||||
vars:
|
|
||||||
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
run:
|
||||||
cmds:
|
summary: 运行 Windows 可执行文件(仅在 Windows 可用)
|
||||||
- '{{.OUTPUT}}'
|
vars:
|
||||||
|
OUTPUT: '{{.OUTPUT | default (printf "%s/%s.exe" .BIN_DIR .APP_NAME)}}'
|
||||||
|
cmds:
|
||||||
|
- '{{.OUTPUT}}'
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/views/ModelConfig.vue",
|
"file": "src/views/ModelConfig.vue",
|
||||||
"line": 256,
|
"line": 255,
|
||||||
"column": 1
|
"column": 165
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -137,8 +137,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/views/Config.vue",
|
"file": "src/views/Config.vue",
|
||||||
"line": 54,
|
"line": 53,
|
||||||
"column": 1
|
"column": 48
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -173,8 +173,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/HomeMetricsCard.vue",
|
"file": "src/components/HomeMetricsCard.vue",
|
||||||
"line": 338,
|
"line": 337,
|
||||||
"column": 1
|
"column": 65
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -400,8 +400,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/views/Config.vue",
|
"file": "src/views/Config.vue",
|
||||||
"line": 68,
|
"line": 67,
|
||||||
"column": 1
|
"column": 48
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -541,8 +541,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/ModelAdapterTestCard.vue",
|
"file": "src/components/ModelAdapterTestCard.vue",
|
||||||
"line": 151,
|
"line": 150,
|
||||||
"column": 1
|
"column": 7
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -589,8 +589,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/ModelAdapterTestCard.vue",
|
"file": "src/components/ModelAdapterTestCard.vue",
|
||||||
"line": 130,
|
"line": 129,
|
||||||
"column": 1
|
"column": 60
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -698,8 +698,8 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"file": "src/components/CursorAccountCard.vue",
|
"file": "src/components/CursorAccountCard.vue",
|
||||||
"line": 164,
|
"line": 163,
|
||||||
"column": 1
|
"column": 11
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -948,8 +948,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/ModelAdapterTestCard.vue",
|
"file": "src/components/ModelAdapterTestCard.vue",
|
||||||
"line": 125,
|
"line": 124,
|
||||||
"column": 1
|
"column": 9
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1569,8 +1569,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/HomeMetricsCard.vue",
|
"file": "src/components/HomeMetricsCard.vue",
|
||||||
"line": 390,
|
"line": 389,
|
||||||
"column": 1
|
"column": 65
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1822,8 +1822,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/HomeMetricsCard.vue",
|
"file": "src/components/HomeMetricsCard.vue",
|
||||||
"line": 342,
|
"line": 341,
|
||||||
"column": 1
|
"column": 23
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -1995,8 +1995,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/views/ModelEditor.vue",
|
"file": "src/views/ModelEditor.vue",
|
||||||
"line": 449,
|
"line": 448,
|
||||||
"column": 1
|
"column": 97
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2772,8 +2772,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/CursorAccountCard.vue",
|
"file": "src/components/CursorAccountCard.vue",
|
||||||
"line": 146,
|
"line": 145,
|
||||||
"column": 1
|
"column": 53
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2905,8 +2905,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/views/Config.vue",
|
"file": "src/views/Config.vue",
|
||||||
"line": 80,
|
"line": 79,
|
||||||
"column": 1
|
"column": 48
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -2982,8 +2982,8 @@
|
|||||||
"refs": [
|
"refs": [
|
||||||
{
|
{
|
||||||
"file": "src/components/CursorAccountCard.vue",
|
"file": "src/components/CursorAccountCard.vue",
|
||||||
"line": 149,
|
"line": 148,
|
||||||
"column": 1
|
"column": 81
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package execbridge
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
@@ -2285,6 +2286,18 @@ func buildReadMcpResourceCompletedToolCall(argsJSON []byte, result *agentv1.Read
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSupportedReadImage(data []byte) bool {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
switch strings.ToLower(strings.TrimSpace(http.DetectContentType(data))) {
|
||||||
|
case "image/png", "image/jpeg", "image/gif", "image/webp":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// convertReadResultToReadToolResult 把 `ReadResult` 映射为 `ReadToolResult`。
|
// convertReadResultToReadToolResult 把 `ReadResult` 映射为 `ReadToolResult`。
|
||||||
func convertReadResultToReadToolResult(result *agentv1.ReadResult) *agentv1.ReadToolResult {
|
func convertReadResultToReadToolResult(result *agentv1.ReadResult) *agentv1.ReadToolResult {
|
||||||
if result == nil {
|
if result == nil {
|
||||||
@@ -2318,7 +2331,9 @@ func convertReadResultToReadToolResult(result *agentv1.ReadResult) *agentv1.Read
|
|||||||
if content != "" {
|
if content != "" {
|
||||||
toolSuccess.Output = &agentv1.ReadToolSuccess_Content{Content: content}
|
toolSuccess.Output = &agentv1.ReadToolSuccess_Content{Content: content}
|
||||||
} else if len(data) > 0 {
|
} else if len(data) > 0 {
|
||||||
if len(data) > readReplayBinaryLimit {
|
if isSupportedReadImage(data) {
|
||||||
|
toolSuccess.Output = &agentv1.ReadToolSuccess_Data{Data: append([]byte(nil), data...)}
|
||||||
|
} else if len(data) > readReplayBinaryLimit {
|
||||||
toolSuccess.ExceededLimit = true
|
toolSuccess.ExceededLimit = true
|
||||||
toolSuccess.Output = &agentv1.ReadToolSuccess_Content{
|
toolSuccess.Output = &agentv1.ReadToolSuccess_Content{
|
||||||
Content: replayTruncationNotice("Read binary data", readReplayBinaryLimit, 0, len(data)),
|
Content: replayTruncationNotice("Read binary data", readReplayBinaryLimit, 0, len(data)),
|
||||||
|
|||||||
@@ -1126,7 +1126,16 @@ func isAnthropicCacheableBlock(block map[string]any) bool {
|
|||||||
case contentPartTypeText:
|
case contentPartTypeText:
|
||||||
return strings.TrimSpace(anthropicStringField(block, "text")) != ""
|
return strings.TrimSpace(anthropicStringField(block, "text")) != ""
|
||||||
case "tool_result":
|
case "tool_result":
|
||||||
return strings.TrimSpace(anthropicStringField(block, "content")) != ""
|
switch content := block["content"].(type) {
|
||||||
|
case string:
|
||||||
|
return strings.TrimSpace(content) != ""
|
||||||
|
case []map[string]any:
|
||||||
|
return len(content) > 0
|
||||||
|
case []any:
|
||||||
|
return len(content) > 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
case "tool_use":
|
case "tool_use":
|
||||||
return strings.TrimSpace(anthropicStringField(block, "id")) != "" && strings.TrimSpace(anthropicStringField(block, "name")) != ""
|
return strings.TrimSpace(anthropicStringField(block, "id")) != "" && strings.TrimSpace(anthropicStringField(block, "name")) != ""
|
||||||
default:
|
default:
|
||||||
@@ -1168,10 +1177,18 @@ func normalizeAnthropicProviderMessages(input []Message, thinkingEnabled bool, r
|
|||||||
if toolUseID == "" {
|
if toolUseID == "" {
|
||||||
return nil, nil, fmt.Errorf("anthropic tool message requires tool_call_id")
|
return nil, nil, fmt.Errorf("anthropic tool message requires tool_call_id")
|
||||||
}
|
}
|
||||||
|
var content any = message.Content
|
||||||
|
if hasImageContentParts(message.ContentParts) {
|
||||||
|
contentBlocks, err := anthropicContentBlocks(message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
content = contentBlocks
|
||||||
|
}
|
||||||
pendingToolResults = append(pendingToolResults, map[string]any{
|
pendingToolResults = append(pendingToolResults, map[string]any{
|
||||||
"type": "tool_result",
|
"type": "tool_result",
|
||||||
"tool_use_id": toolUseID,
|
"tool_use_id": toolUseID,
|
||||||
"content": message.Content,
|
"content": content,
|
||||||
})
|
})
|
||||||
case "user", "assistant":
|
case "user", "assistant":
|
||||||
flushToolResults()
|
flushToolResults()
|
||||||
|
|||||||
@@ -1967,10 +1967,18 @@ func normalizeOpenAIResponsesInput(messages []Message) (string, []map[string]any
|
|||||||
}
|
}
|
||||||
if role == "tool" && strings.TrimSpace(message.ToolCallID) != "" {
|
if role == "tool" && strings.TrimSpace(message.ToolCallID) != "" {
|
||||||
callID := openAIResponsesToolMessageCallID(message, responsesCallIDs)
|
callID := openAIResponsesToolMessageCallID(message, responsesCallIDs)
|
||||||
|
var output any = openAIResponsesMessageText(message)
|
||||||
|
if hasImageContentParts(message.ContentParts) {
|
||||||
|
content, err := openAIResponsesMessageContent(message, false)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, err
|
||||||
|
}
|
||||||
|
output = content
|
||||||
|
}
|
||||||
items = append(items, map[string]any{
|
items = append(items, map[string]any{
|
||||||
"type": "function_call_output",
|
"type": "function_call_output",
|
||||||
"call_id": callID,
|
"call_id": callID,
|
||||||
"output": openAIResponsesMessageText(message),
|
"output": output,
|
||||||
})
|
})
|
||||||
activeAssistantReasoningKey = ""
|
activeAssistantReasoningKey = ""
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"google.golang.org/protobuf/encoding/protojson"
|
"google.golang.org/protobuf/encoding/protojson"
|
||||||
@@ -215,6 +216,7 @@ func (projector *HistoryProjector) ProjectPromptReplay(conversation *Conversatio
|
|||||||
if ok {
|
if ok {
|
||||||
replayMessage.Name = toolName
|
replayMessage.Name = toolName
|
||||||
replayMessage.Content = limitProjectedToolResultReplay(toolName, replayMessage.Content, payload.ResultText, true, historicalToolResult)
|
replayMessage.Content = limitProjectedToolResultReplay(toolName, replayMessage.Content, payload.ResultText, true, historicalToolResult)
|
||||||
|
attachReadImageContentParts(&replayMessage, toolCall)
|
||||||
messages = append(messages, toModelMessage(replayMessage))
|
messages = append(messages, toModelMessage(replayMessage))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -249,12 +251,13 @@ func (projector *HistoryProjector) ProjectPromptReplay(conversation *Conversatio
|
|||||||
replayMessages[index].OpenAIResponsesReasoningSummary = append(json.RawMessage(nil), payload.ReasoningSummary...)
|
replayMessages[index].OpenAIResponsesReasoningSummary = append(json.RawMessage(nil), payload.ReasoningSummary...)
|
||||||
applyPromptProviderMetadataToFirstToolCall(&replayMessages[index], payload.ProviderItemID, payload.ProviderCallID, payload.ProviderStatus)
|
applyPromptProviderMetadataToFirstToolCall(&replayMessages[index], payload.ProviderItemID, payload.ProviderCallID, payload.ProviderStatus)
|
||||||
}
|
}
|
||||||
for _, replay := range replayMessages {
|
for index := range replayMessages {
|
||||||
if strings.TrimSpace(replay.Role) == "tool" {
|
if strings.TrimSpace(replayMessages[index].Role) == "tool" {
|
||||||
toolName := firstNonEmpty(strings.TrimSpace(replay.Name), strings.TrimSpace(payload.ToolName))
|
toolName := firstNonEmpty(strings.TrimSpace(replayMessages[index].Name), strings.TrimSpace(payload.ToolName))
|
||||||
replay.Content = limitProjectedToolResultReplay(toolName, replay.Content, payload.ResultText, true, historicalToolResult)
|
replayMessages[index].Content = limitProjectedToolResultReplay(toolName, replayMessages[index].Content, payload.ResultText, true, historicalToolResult)
|
||||||
|
attachReadImageContentParts(&replayMessages[index], toolCall)
|
||||||
}
|
}
|
||||||
messages = append(messages, toModelMessage(replay))
|
messages = append(messages, toModelMessage(replayMessages[index]))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -305,6 +308,58 @@ func (projector *HistoryProjector) ProjectPromptReplay(conversation *Conversatio
|
|||||||
return normalizeReplayMessageSequence(messages), nil
|
return normalizeReplayMessageSequence(messages), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func attachReadImageContentParts(message *promptengine.Message, toolCall *agentv1.ToolCall) {
|
||||||
|
if message == nil || toolCall == nil || strings.TrimSpace(message.Role) != "tool" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
readToolCall := toolCall.GetReadToolCall()
|
||||||
|
if readToolCall == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success := readToolCall.GetResult().GetSuccess()
|
||||||
|
if success == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
data := success.GetData()
|
||||||
|
mimeType := supportedReadImageMIMEType(data)
|
||||||
|
if mimeType == "" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
path := firstNonEmpty(strings.TrimSpace(success.GetPath()), strings.TrimSpace(readToolCall.GetArgs().GetPath()))
|
||||||
|
summary := fmt.Sprintf("read image path=%q mime=%s bytes=%d", path, mimeType, len(data))
|
||||||
|
if fileSize := success.GetFileSize(); fileSize > 0 && uint64(fileSize) != uint64(len(data)) {
|
||||||
|
summary += fmt.Sprintf(" file_size=%d", fileSize)
|
||||||
|
}
|
||||||
|
if success.GetExceededLimit() {
|
||||||
|
summary += " truncated=true"
|
||||||
|
}
|
||||||
|
message.Content = summary
|
||||||
|
message.ContentParts = []promptengine.ContentPart{
|
||||||
|
{Type: "text", Text: summary},
|
||||||
|
{
|
||||||
|
Type: "image",
|
||||||
|
Image: &promptengine.ImageContent{
|
||||||
|
MIMEType: mimeType,
|
||||||
|
Path: path,
|
||||||
|
Data: append([]byte(nil), data...),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func supportedReadImageMIMEType(data []byte) string {
|
||||||
|
if len(data) == 0 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
mimeType := strings.ToLower(strings.TrimSpace(http.DetectContentType(data)))
|
||||||
|
switch mimeType {
|
||||||
|
case "image/png", "image/jpeg", "image/gif", "image/webp":
|
||||||
|
return mimeType
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func compactedPromptProjectionEntries(entries []HistoryEntry) []HistoryEntry {
|
func compactedPromptProjectionEntries(entries []HistoryEntry) []HistoryEntry {
|
||||||
if len(entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
Reference in New Issue
Block a user