| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519 |
- package antigravity
- import (
- "fmt"
- "strings"
- )
- // CleanJSONSchema 清理 JSON Schema,移除 Antigravity/Gemini 不支持的字段
- // 参考 Antigravity-Manager/src-tauri/src/proxy/common/json_schema.rs 实现
- // 确保 schema 符合 JSON Schema draft 2020-12 且适配 Gemini v1internal
- func CleanJSONSchema(schema map[string]any) map[string]any {
- if schema == nil {
- return nil
- }
- // 0. 预处理:展开 $ref (Schema Flattening)
- // (Go map 是引用的,直接修改 schema)
- flattenRefs(schema, extractDefs(schema))
- // 递归清理
- cleaned := cleanJSONSchemaRecursive(schema)
- result, ok := cleaned.(map[string]any)
- if !ok {
- return nil
- }
- return result
- }
- // extractDefs 提取并移除定义的 helper
- func extractDefs(schema map[string]any) map[string]any {
- defs := make(map[string]any)
- if d, ok := schema["$defs"].(map[string]any); ok {
- for k, v := range d {
- defs[k] = v
- }
- delete(schema, "$defs")
- }
- if d, ok := schema["definitions"].(map[string]any); ok {
- for k, v := range d {
- defs[k] = v
- }
- delete(schema, "definitions")
- }
- return defs
- }
- // flattenRefs 递归展开 $ref
- func flattenRefs(schema map[string]any, defs map[string]any) {
- if len(defs) == 0 {
- return // 无需展开
- }
- // 检查并替换 $ref
- if ref, ok := schema["$ref"].(string); ok {
- delete(schema, "$ref")
- // 解析引用名 (例如 #/$defs/MyType -> MyType)
- parts := strings.Split(ref, "/")
- refName := parts[len(parts)-1]
- if defSchema, exists := defs[refName]; exists {
- if defMap, ok := defSchema.(map[string]any); ok {
- // 合并定义内容 (不覆盖现有 key)
- for k, v := range defMap {
- if _, has := schema[k]; !has {
- schema[k] = deepCopy(v) // 需深拷贝避免共享引用
- }
- }
- // 递归处理刚刚合并进来的内容
- flattenRefs(schema, defs)
- }
- }
- }
- // 遍历子节点
- for _, v := range schema {
- if subMap, ok := v.(map[string]any); ok {
- flattenRefs(subMap, defs)
- } else if subArr, ok := v.([]any); ok {
- for _, item := range subArr {
- if itemMap, ok := item.(map[string]any); ok {
- flattenRefs(itemMap, defs)
- }
- }
- }
- }
- }
- // deepCopy 深拷贝 (简单实现,仅针对 JSON 类型)
- func deepCopy(src any) any {
- if src == nil {
- return nil
- }
- switch v := src.(type) {
- case map[string]any:
- dst := make(map[string]any)
- for k, val := range v {
- dst[k] = deepCopy(val)
- }
- return dst
- case []any:
- dst := make([]any, len(v))
- for i, val := range v {
- dst[i] = deepCopy(val)
- }
- return dst
- default:
- return src
- }
- }
- // cleanJSONSchemaRecursive 递归核心清理逻辑
- // 返回处理后的值 (通常是 input map,但可能修改内部结构)
- func cleanJSONSchemaRecursive(value any) any {
- schemaMap, ok := value.(map[string]any)
- if !ok {
- return value
- }
- // 0. [NEW] 合并 allOf
- mergeAllOf(schemaMap)
- // 1. [CRITICAL] 深度递归处理子项
- if props, ok := schemaMap["properties"].(map[string]any); ok {
- for _, v := range props {
- cleanJSONSchemaRecursive(v)
- }
- // Go 中不需要像 Rust 那样显式处理 nullable_keys remove required,
- // 因为我们在子项处理中会正确设置 type 和 description
- } else if items, ok := schemaMap["items"]; ok {
- // [FIX] Gemini 期望 "items" 是单个 Schema 对象(列表验证),而不是数组(元组验证)。
- if itemsArr, ok := items.([]any); ok {
- // 策略:将元组 [A, B] 视为 A、B 中的最佳匹配项。
- best := extractBestSchemaFromUnion(itemsArr)
- if best == nil {
- // 回退到通用字符串
- best = map[string]any{"type": "string"}
- }
- // 用处理后的对象替换原有数组
- cleanedBest := cleanJSONSchemaRecursive(best)
- schemaMap["items"] = cleanedBest
- } else {
- cleanJSONSchemaRecursive(items)
- }
- } else {
- // 遍历所有值递归
- for _, v := range schemaMap {
- if _, isMap := v.(map[string]any); isMap {
- cleanJSONSchemaRecursive(v)
- } else if arr, isArr := v.([]any); isArr {
- for _, item := range arr {
- cleanJSONSchemaRecursive(item)
- }
- }
- }
- }
- // 2. [FIX] 处理 anyOf/oneOf 联合类型: 合并属性而非直接删除
- var unionArray []any
- typeStr, _ := schemaMap["type"].(string)
- if typeStr == "" || typeStr == "object" {
- if anyOf, ok := schemaMap["anyOf"].([]any); ok {
- unionArray = anyOf
- } else if oneOf, ok := schemaMap["oneOf"].([]any); ok {
- unionArray = oneOf
- }
- }
- if len(unionArray) > 0 {
- if bestBranch := extractBestSchemaFromUnion(unionArray); bestBranch != nil {
- if bestMap, ok := bestBranch.(map[string]any); ok {
- // 合并分支内容
- for k, v := range bestMap {
- if k == "properties" {
- targetProps, _ := schemaMap["properties"].(map[string]any)
- if targetProps == nil {
- targetProps = make(map[string]any)
- schemaMap["properties"] = targetProps
- }
- if sourceProps, ok := v.(map[string]any); ok {
- for pk, pv := range sourceProps {
- if _, exists := targetProps[pk]; !exists {
- targetProps[pk] = deepCopy(pv)
- }
- }
- }
- } else if k == "required" {
- targetReq, _ := schemaMap["required"].([]any)
- if sourceReq, ok := v.([]any); ok {
- for _, rv := range sourceReq {
- // 简单的去重添加
- exists := false
- for _, tr := range targetReq {
- if tr == rv {
- exists = true
- break
- }
- }
- if !exists {
- targetReq = append(targetReq, rv)
- }
- }
- schemaMap["required"] = targetReq
- }
- } else if _, exists := schemaMap[k]; !exists {
- schemaMap[k] = deepCopy(v)
- }
- }
- }
- }
- }
- // 3. [SAFETY] 检查当前对象是否为 JSON Schema 节点
- looksLikeSchema := hasKey(schemaMap, "type") ||
- hasKey(schemaMap, "properties") ||
- hasKey(schemaMap, "items") ||
- hasKey(schemaMap, "enum") ||
- hasKey(schemaMap, "anyOf") ||
- hasKey(schemaMap, "oneOf") ||
- hasKey(schemaMap, "allOf")
- if looksLikeSchema {
- // 4. [ROBUST] 约束迁移
- migrateConstraints(schemaMap)
- // 5. [CRITICAL] 白名单过滤
- allowedFields := map[string]bool{
- "type": true,
- "description": true,
- "properties": true,
- "required": true,
- "items": true,
- "enum": true,
- "title": true,
- }
- for k := range schemaMap {
- if !allowedFields[k] {
- delete(schemaMap, k)
- }
- }
- // 6. [SAFETY] 处理空 Object
- if t, _ := schemaMap["type"].(string); t == "object" {
- hasProps := false
- if props, ok := schemaMap["properties"].(map[string]any); ok && len(props) > 0 {
- hasProps = true
- }
- if !hasProps {
- schemaMap["properties"] = map[string]any{
- "reason": map[string]any{
- "type": "string",
- "description": "Reason for calling this tool",
- },
- }
- schemaMap["required"] = []any{"reason"}
- }
- }
- // 7. [SAFETY] Required 字段对齐
- if props, ok := schemaMap["properties"].(map[string]any); ok {
- if req, ok := schemaMap["required"].([]any); ok {
- var validReq []any
- for _, r := range req {
- if rStr, ok := r.(string); ok {
- if _, exists := props[rStr]; exists {
- validReq = append(validReq, r)
- }
- }
- }
- if len(validReq) > 0 {
- schemaMap["required"] = validReq
- } else {
- delete(schemaMap, "required")
- }
- }
- }
- // 8. 处理 type 字段 (Lowercase + Nullable 提取)
- isEffectivelyNullable := false
- if typeVal, exists := schemaMap["type"]; exists {
- var selectedType string
- switch v := typeVal.(type) {
- case string:
- lower := strings.ToLower(v)
- if lower == "null" {
- isEffectivelyNullable = true
- selectedType = "string" // fallback
- } else {
- selectedType = lower
- }
- case []any:
- // ["string", "null"]
- for _, t := range v {
- if ts, ok := t.(string); ok {
- lower := strings.ToLower(ts)
- if lower == "null" {
- isEffectivelyNullable = true
- } else if selectedType == "" {
- selectedType = lower
- }
- }
- }
- if selectedType == "" {
- selectedType = "string"
- }
- }
- schemaMap["type"] = selectedType
- } else {
- // 默认 object 如果有 properties (虽然上面白名单过滤可能删了 type 如果它不在... 但 type 必在 allowlist)
- // 如果没有 type,但有 properties,补一个
- if hasKey(schemaMap, "properties") {
- schemaMap["type"] = "object"
- } else {
- // 默认为 string ? or object? Gemini 通常需要明确 type
- schemaMap["type"] = "object"
- }
- }
- if isEffectivelyNullable {
- desc, _ := schemaMap["description"].(string)
- if !strings.Contains(desc, "nullable") {
- if desc != "" {
- desc += " "
- }
- desc += "(nullable)"
- schemaMap["description"] = desc
- }
- }
- // 9. Enum 值强制转字符串
- if enumVals, ok := schemaMap["enum"].([]any); ok {
- hasNonString := false
- for i, val := range enumVals {
- if _, isStr := val.(string); !isStr {
- hasNonString = true
- if val == nil {
- enumVals[i] = "null"
- } else {
- enumVals[i] = fmt.Sprintf("%v", val)
- }
- }
- }
- // If we mandated string values, we must ensure type is string
- if hasNonString {
- schemaMap["type"] = "string"
- }
- }
- }
- return schemaMap
- }
- func hasKey(m map[string]any, k string) bool {
- _, ok := m[k]
- return ok
- }
- func migrateConstraints(m map[string]any) {
- constraints := []struct {
- key string
- label string
- }{
- {"minLength", "minLen"},
- {"maxLength", "maxLen"},
- {"pattern", "pattern"},
- {"minimum", "min"},
- {"maximum", "max"},
- {"multipleOf", "multipleOf"},
- {"exclusiveMinimum", "exclMin"},
- {"exclusiveMaximum", "exclMax"},
- {"minItems", "minItems"},
- {"maxItems", "maxItems"},
- {"propertyNames", "propertyNames"},
- {"format", "format"},
- }
- var hints []string
- for _, c := range constraints {
- if val, ok := m[c.key]; ok && val != nil {
- hints = append(hints, fmt.Sprintf("%s: %v", c.label, val))
- }
- }
- if len(hints) > 0 {
- suffix := fmt.Sprintf(" [Constraint: %s]", strings.Join(hints, ", "))
- desc, _ := m["description"].(string)
- if !strings.Contains(desc, suffix) {
- m["description"] = desc + suffix
- }
- }
- }
- // mergeAllOf 合并 allOf
- func mergeAllOf(m map[string]any) {
- allOf, ok := m["allOf"].([]any)
- if !ok {
- return
- }
- delete(m, "allOf")
- mergedProps := make(map[string]any)
- mergedReq := make(map[string]bool)
- otherFields := make(map[string]any)
- for _, sub := range allOf {
- if subMap, ok := sub.(map[string]any); ok {
- // Props
- if props, ok := subMap["properties"].(map[string]any); ok {
- for k, v := range props {
- mergedProps[k] = v
- }
- }
- // Required
- if reqs, ok := subMap["required"].([]any); ok {
- for _, r := range reqs {
- if s, ok := r.(string); ok {
- mergedReq[s] = true
- }
- }
- }
- // Others
- for k, v := range subMap {
- if k != "properties" && k != "required" && k != "allOf" {
- if _, exists := otherFields[k]; !exists {
- otherFields[k] = v
- }
- }
- }
- }
- }
- // Apply
- for k, v := range otherFields {
- if _, exists := m[k]; !exists {
- m[k] = v
- }
- }
- if len(mergedProps) > 0 {
- existProps, _ := m["properties"].(map[string]any)
- if existProps == nil {
- existProps = make(map[string]any)
- m["properties"] = existProps
- }
- for k, v := range mergedProps {
- if _, exists := existProps[k]; !exists {
- existProps[k] = v
- }
- }
- }
- if len(mergedReq) > 0 {
- existReq, _ := m["required"].([]any)
- var validReqs []any
- for _, r := range existReq {
- if s, ok := r.(string); ok {
- validReqs = append(validReqs, s)
- delete(mergedReq, s) // already exists
- }
- }
- // append new
- for r := range mergedReq {
- validReqs = append(validReqs, r)
- }
- m["required"] = validReqs
- }
- }
- // extractBestSchemaFromUnion 从 anyOf/oneOf 中选取最佳分支
- func extractBestSchemaFromUnion(unionArray []any) any {
- var bestOption any
- bestScore := -1
- for _, item := range unionArray {
- score := scoreSchemaOption(item)
- if score > bestScore {
- bestScore = score
- bestOption = item
- }
- }
- return bestOption
- }
- func scoreSchemaOption(val any) int {
- m, ok := val.(map[string]any)
- if !ok {
- return 0
- }
- typeStr, _ := m["type"].(string)
- if hasKey(m, "properties") || typeStr == "object" {
- return 3
- }
- if hasKey(m, "items") || typeStr == "array" {
- return 2
- }
- if typeStr != "" && typeStr != "null" {
- return 1
- }
- return 0
- }
- // DeepCleanUndefined 深度清理值为 "[undefined]" 的字段
- func DeepCleanUndefined(value any) {
- if value == nil {
- return
- }
- switch v := value.(type) {
- case map[string]any:
- for k, val := range v {
- if s, ok := val.(string); ok && s == "[undefined]" {
- delete(v, k)
- continue
- }
- DeepCleanUndefined(val)
- }
- case []any:
- for _, val := range v {
- DeepCleanUndefined(val)
- }
- }
- }
|