Skip to content

Commit 39f96fc

Browse files
committed
fix(gateway): drop encrypted tool schema markers
refs #35
1 parent 6441e83 commit 39f96fc

2 files changed

Lines changed: 141 additions & 0 deletions

File tree

lib/codex_pooler/gateway/payloads/payload_normalizer.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ defmodule CodexPooler.Gateway.Payloads.PayloadNormalizer do
114114
|> Map.drop(["request_id"])
115115
|> Map.put_new("type", "response.create")
116116
|> Map.put_new("instructions", "")
117+
|> remove_backend_codex_encrypted_tool_schema_markers()
117118
|> maybe_put_websocket_responses_lite_client_metadata(request_options)
118119
end
119120

@@ -145,8 +146,27 @@ defmodule CodexPooler.Gateway.Payloads.PayloadNormalizer do
145146
|> maybe_drop_backend_codex_previous_response_id(opts)
146147
|> Map.put_new("instructions", "")
147148
|> normalize_backend_codex_http_input()
149+
|> remove_backend_codex_encrypted_tool_schema_markers()
148150
end
149151

152+
defp remove_backend_codex_encrypted_tool_schema_markers(%{"tools" => tools} = payload)
153+
when is_list(tools) do
154+
Map.put(payload, "tools", Enum.map(tools, &remove_schema_encrypted_markers/1))
155+
end
156+
157+
defp remove_backend_codex_encrypted_tool_schema_markers(payload), do: payload
158+
159+
defp remove_schema_encrypted_markers(%{} = value) do
160+
value
161+
|> Map.delete("encrypted")
162+
|> Map.new(fn {key, value} -> {key, remove_schema_encrypted_markers(value)} end)
163+
end
164+
165+
defp remove_schema_encrypted_markers(value) when is_list(value),
166+
do: Enum.map(value, &remove_schema_encrypted_markers/1)
167+
168+
defp remove_schema_encrypted_markers(value), do: value
169+
150170
defp maybe_drop_backend_codex_previous_response_id(payload, _opts) do
151171
if backend_codex_tool_result_continuation?(payload),
152172
do: payload,

test/codex_pooler/gateway/payloads/payload_normalizer_test.exs

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,80 @@ defmodule CodexPooler.Gateway.Payloads.PayloadNormalizerTest do
77
alias CodexPooler.Gateway.Payloads.RequestOptions
88

99
describe "upstream_payload/4" do
10+
test "removes backend Codex encrypted tool schema markers from HTTP upstream JSON" do
11+
payload = encrypted_tool_schema_payload()
12+
request_options = RequestOptions.build(%{}, "/backend-api/codex/responses", payload)
13+
model = %Model{upstream_model_id: "provider-model"}
14+
15+
assert {:ok, encoded} =
16+
PayloadNormalizer.upstream_payload(
17+
payload,
18+
model,
19+
"/backend-api/codex/responses",
20+
request_options
21+
)
22+
23+
upstream = Jason.decode!(encoded)
24+
25+
assert get_in(upstream, ["tools", Access.at(0), "parameters", "properties", "message"]) ==
26+
%{
27+
"description" => "Initial plain-text task for the new agent.",
28+
"type" => "string"
29+
}
30+
31+
assert get_in(upstream, [
32+
"tools",
33+
Access.at(1),
34+
"function",
35+
"parameters",
36+
"properties",
37+
"message"
38+
]) ==
39+
%{
40+
"description" => "Message text to queue on the target agent.",
41+
"type" => "string"
42+
}
43+
end
44+
45+
test "removes backend Codex encrypted tool schema markers from websocket upstream JSON" do
46+
payload = encrypted_tool_schema_payload()
47+
48+
request_options =
49+
%{}
50+
|> RequestOptions.build("/backend-api/codex/responses", payload)
51+
|> RequestOptions.for_websocket(payload)
52+
53+
model = %Model{upstream_model_id: "provider-model"}
54+
55+
assert {:ok, encoded} =
56+
PayloadNormalizer.upstream_payload(
57+
payload,
58+
model,
59+
"/backend-api/codex/responses",
60+
request_options
61+
)
62+
63+
upstream = Jason.decode!(encoded)
64+
assert upstream["type"] == "response.create"
65+
66+
refute Map.has_key?(
67+
get_in(upstream, ["tools", Access.at(0), "parameters", "properties", "message"]),
68+
"encrypted"
69+
)
70+
71+
refute Map.has_key?(
72+
get_in(upstream, [
73+
"tools",
74+
Access.at(1),
75+
"function",
76+
"parameters",
77+
"properties",
78+
"message"
79+
]),
80+
"encrypted"
81+
)
82+
end
83+
1084
test "omits absent, auto, and default service tiers while preserving concrete tiers upstream" do
1185
model = %Model{upstream_model_id: "provider-model"}
1286

@@ -183,4 +257,51 @@ defmodule CodexPooler.Gateway.Payloads.PayloadNormalizerTest do
183257
assert Jason.decode!(encoded)["service_tier"] == "priority"
184258
end
185259
end
260+
261+
defp encrypted_tool_schema_payload do
262+
%{
263+
"model" => "gpt-5.5",
264+
"input" => [%{"role" => "user", "content" => "hello"}],
265+
"tools" => [
266+
%{
267+
"type" => "function",
268+
"name" => "spawn_agent",
269+
"strict" => false,
270+
"parameters" => %{
271+
"type" => "object",
272+
"properties" => %{
273+
"message" => %{
274+
"type" => "string",
275+
"description" => "Initial plain-text task for the new agent.",
276+
"encrypted" => true
277+
},
278+
"task_name" => %{"type" => "string"}
279+
},
280+
"required" => ["task_name", "message"],
281+
"additionalProperties" => false
282+
}
283+
},
284+
%{
285+
"type" => "function",
286+
"function" => %{
287+
"name" => "send_message",
288+
"strict" => false,
289+
"parameters" => %{
290+
"type" => "object",
291+
"properties" => %{
292+
"message" => %{
293+
"type" => "string",
294+
"description" => "Message text to queue on the target agent.",
295+
"encrypted" => true
296+
},
297+
"target" => %{"type" => "string"}
298+
},
299+
"required" => ["target", "message"],
300+
"additionalProperties" => false
301+
}
302+
}
303+
}
304+
]
305+
}
306+
end
186307
end

0 commit comments

Comments
 (0)