@@ -119,6 +119,90 @@ describe("buildProviderToolCompatFamilyHooks", () => {
|
119 | 119 | ).toStrictEqual([]); |
120 | 120 | }); |
121 | 121 | |
| 122 | +it("preserves string-const unions as a flat enum for the deepseek family", () => { |
| 123 | +// Regression for https://github.com/openclaw/openclaw/issues/86468 — |
| 124 | +// Typebox `Type.Union([Type.Literal(...)])` collapses to anyOf of consts; |
| 125 | +// the previous normalizer kept only the first const, hiding every other |
| 126 | +// literal from the model. |
| 127 | +const hooks = buildProviderToolCompatFamilyHooks("deepseek"); |
| 128 | +const tools = [ |
| 129 | +{ |
| 130 | +name: "feishu_update_doc", |
| 131 | +description: "", |
| 132 | +parameters: { |
| 133 | +type: "object", |
| 134 | +properties: { |
| 135 | +mode: { |
| 136 | +description: "更新模式(必填)", |
| 137 | +anyOf: [ |
| 138 | +{ const: "overwrite", type: "string" }, |
| 139 | +{ const: "append", type: "string" }, |
| 140 | +{ const: "replace_range", type: "string" }, |
| 141 | +], |
| 142 | +}, |
| 143 | +optional_mode: { |
| 144 | +anyOf: [ |
| 145 | +{ const: "a", type: "string" }, |
| 146 | +{ const: "b", type: "string" }, |
| 147 | +{ type: "null" }, |
| 148 | +], |
| 149 | +}, |
| 150 | +single_const: { |
| 151 | +anyOf: [{ const: "only", type: "string" }], |
| 152 | +}, |
| 153 | +}, |
| 154 | +required: ["mode"], |
| 155 | +}, |
| 156 | +}, |
| 157 | +] as never; |
| 158 | + |
| 159 | +const normalized = hooks.normalizeToolSchemas({ |
| 160 | +provider: "deepseek", |
| 161 | +modelId: "deepseek-v4-pro", |
| 162 | +modelApi: "openai-completions", |
| 163 | +model: { |
| 164 | +provider: "deepseek", |
| 165 | +api: "openai-completions", |
| 166 | +id: "deepseek-v4-pro", |
| 167 | +} as never, |
| 168 | + tools, |
| 169 | +}); |
| 170 | + |
| 171 | +expect(normalized[0]?.parameters).toEqual({ |
| 172 | +type: "object", |
| 173 | +properties: { |
| 174 | +mode: { |
| 175 | +description: "更新模式(必填)", |
| 176 | +type: "string", |
| 177 | +enum: ["overwrite", "append", "replace_range"], |
| 178 | +}, |
| 179 | +optional_mode: { |
| 180 | +type: "string", |
| 181 | +enum: ["a", "b"], |
| 182 | +nullable: true, |
| 183 | +}, |
| 184 | +single_const: { |
| 185 | +const: "only", |
| 186 | +type: "string", |
| 187 | +}, |
| 188 | +}, |
| 189 | +required: ["mode"], |
| 190 | +}); |
| 191 | +expect( |
| 192 | +hooks.inspectToolSchemas({ |
| 193 | +provider: "deepseek", |
| 194 | +modelId: "deepseek-v4-pro", |
| 195 | +modelApi: "openai-completions", |
| 196 | +model: { |
| 197 | +provider: "deepseek", |
| 198 | +api: "openai-completions", |
| 199 | +id: "deepseek-v4-pro", |
| 200 | +} as never, |
| 201 | +tools: normalized, |
| 202 | +}), |
| 203 | +).toStrictEqual([]); |
| 204 | +}); |
| 205 | + |
122 | 206 | it("normalizes parameter-free and typed-object schemas for the openai family", () => { |
123 | 207 | const hooks = buildProviderToolCompatFamilyHooks("openai"); |
124 | 208 | const tools = [ |
|