@velum-labs/routekit-gateway 0.18.4 → 0.18.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -14,9 +14,13 @@ export type BedrockProviderSourceOptions = {
14
14
  export declare const BEDROCK_OPENAI_ALLOWLIST: readonly ["openai.gpt-5.4", "openai.gpt-5.5", "openai.gpt-5.6-sol", "openai.gpt-5.6-terra", "openai.gpt-5.6-luna"];
15
15
  export declare function isBedrockOpenAiModel(modelId: string): boolean;
16
16
  /**
17
- * Codex advertises `web_search_tool_type: "text_and_image"` and sends
18
- * `search_content_types` on `{ type: "web_search" }`. Bedrock mantle accepts
19
- * the tool but rejects that field. Native OpenAI Responses is unchanged.
17
+ * Codex talks to RouteKit as OpenAI-compatible, so it sends OpenAI/Codex
18
+ * protocol items that Bedrock mantle rejects. Native OpenAI is unchanged.
19
+ *
20
+ * - `search_content_types` on `web_search*`
21
+ * - `agent_message` (Codex 0.147 subagent handoff)
22
+ * - `encrypted_content` content parts (same 400 as `agent_message`)
23
+ * - `compaction` / `reasoning` encrypted blobs without `rsn_` / `smry_`
20
24
  */
21
25
  export declare function sanitizeBedrockMantleRequestBody(body: unknown): unknown;
22
26
  export declare function toBedrockConverseInput(body: unknown): ConverseCommandInput;
@@ -15,27 +15,93 @@ export function isBedrockOpenAiModel(modelId) {
15
15
  return BEDROCK_OPENAI_MODEL.test(modelId);
16
16
  }
17
17
  /**
18
- * Codex advertises `web_search_tool_type: "text_and_image"` and sends
19
- * `search_content_types` on `{ type: "web_search" }`. Bedrock mantle accepts
20
- * the tool but rejects that field. Native OpenAI Responses is unchanged.
18
+ * Codex talks to RouteKit as OpenAI-compatible, so it sends OpenAI/Codex
19
+ * protocol items that Bedrock mantle rejects. Native OpenAI is unchanged.
20
+ *
21
+ * - `search_content_types` on `web_search*`
22
+ * - `agent_message` (Codex 0.147 subagent handoff)
23
+ * - `encrypted_content` content parts (same 400 as `agent_message`)
24
+ * - `compaction` / `reasoning` encrypted blobs without `rsn_` / `smry_`
21
25
  */
22
26
  export function sanitizeBedrockMantleRequestBody(body) {
23
27
  const payload = record(body);
24
- if (payload === undefined || !Array.isArray(payload.tools))
28
+ if (payload === undefined)
25
29
  return body;
30
+ let next = payload;
26
31
  let changed = false;
27
- const tools = payload.tools.map((tool) => {
28
- const entry = record(tool);
29
- if (entry === undefined)
30
- return tool;
31
- const type = typeof entry.type === "string" ? entry.type : "";
32
- if (!type.startsWith("web_search") || !("search_content_types" in entry))
33
- return tool;
32
+ if (Array.isArray(payload.tools)) {
33
+ const tools = payload.tools.map((tool) => {
34
+ const entry = record(tool);
35
+ if (entry === undefined)
36
+ return tool;
37
+ const type = typeof entry.type === "string" ? entry.type : "";
38
+ if (!type.startsWith("web_search") || !("search_content_types" in entry))
39
+ return tool;
40
+ changed = true;
41
+ const { search_content_types: _ignored, ...rest } = entry;
42
+ return rest;
43
+ });
44
+ if (changed)
45
+ next = { ...next, tools };
46
+ }
47
+ if (Array.isArray(payload.input)) {
48
+ const input = [];
49
+ let inputChanged = false;
50
+ for (const item of payload.input) {
51
+ const sanitized = sanitizeBedrockMantleInputItem(item);
52
+ if (sanitized === undefined) {
53
+ inputChanged = true;
54
+ continue;
55
+ }
56
+ if (sanitized !== item)
57
+ inputChanged = true;
58
+ input.push(sanitized);
59
+ }
60
+ if (inputChanged) {
61
+ changed = true;
62
+ next = { ...next, input };
63
+ }
64
+ }
65
+ return changed ? next : body;
66
+ }
67
+ function recognizedEncryptedPrefix(value) {
68
+ return typeof value === "string" && (value.startsWith("rsn_") || value.startsWith("smry_"));
69
+ }
70
+ function sanitizeBedrockMantleContent(content) {
71
+ if (!Array.isArray(content))
72
+ return { content, changed: false };
73
+ const parts = content.filter((part) => record(part)?.type !== "encrypted_content");
74
+ return { content: parts, changed: parts.length !== content.length };
75
+ }
76
+ function sanitizeBedrockMantleInputItem(item) {
77
+ const entry = record(item);
78
+ if (entry === undefined)
79
+ return item;
80
+ const type = typeof entry.type === "string" ? entry.type : "";
81
+ if (type === "compaction" && "encrypted_content" in entry && !recognizedEncryptedPrefix(entry.encrypted_content)) {
82
+ return undefined;
83
+ }
84
+ if (type === "agent_message") {
85
+ const { content } = sanitizeBedrockMantleContent(entry.content);
86
+ return { type: "message", role: "user", content };
87
+ }
88
+ let next = entry;
89
+ let changed = false;
90
+ if (Array.isArray(entry.content)) {
91
+ const sanitized = sanitizeBedrockMantleContent(entry.content);
92
+ if (sanitized.changed) {
93
+ next = { ...next, content: sanitized.content };
94
+ changed = true;
95
+ }
96
+ }
97
+ if (type === "reasoning" &&
98
+ "encrypted_content" in next &&
99
+ !recognizedEncryptedPrefix(next.encrypted_content)) {
100
+ const { encrypted_content: _ignored, ...rest } = next;
101
+ next = rest;
34
102
  changed = true;
35
- const { search_content_types: _ignored, ...rest } = entry;
36
- return rest;
37
- });
38
- return changed ? { ...payload, tools } : body;
103
+ }
104
+ return changed ? next : item;
39
105
  }
40
106
  function mantleApiKey(env) {
41
107
  const key = env.AWS_BEARER_TOKEN_BEDROCK ?? env.BEDROCK_API_KEY;
@@ -523,6 +523,48 @@ test("sanitizeBedrockMantleRequestBody strips search_content_types from web_sear
523
523
  const untouched = { model: "openai.gpt-5.6-sol", tools: [{ type: "web_search" }] };
524
524
  assert.equal(sanitizeBedrockMantleRequestBody(untouched), untouched);
525
525
  });
526
+ test("sanitizeBedrockMantleRequestBody rewrites Codex agent_message and drops encrypted_content", () => {
527
+ const body = {
528
+ model: "openai.gpt-5.6-sol",
529
+ input: [
530
+ {
531
+ type: "agent_message",
532
+ author: "/root",
533
+ recipient: "/root/package_architecture",
534
+ content: [
535
+ { type: "input_text", text: "Reply with exactly PONG" },
536
+ { type: "encrypted_content", encrypted_content: " " }
537
+ ]
538
+ },
539
+ {
540
+ type: "message",
541
+ role: "user",
542
+ content: [
543
+ { type: "input_text", text: "keep" },
544
+ { type: "encrypted_content", encrypted_content: " " }
545
+ ]
546
+ },
547
+ { type: "compaction", encrypted_content: "not-a-prefix" },
548
+ { type: "reasoning", encrypted_content: "rsn_ok", summary: [] }
549
+ ]
550
+ };
551
+ assert.deepEqual(sanitizeBedrockMantleRequestBody(body), {
552
+ model: "openai.gpt-5.6-sol",
553
+ input: [
554
+ {
555
+ type: "message",
556
+ role: "user",
557
+ content: [{ type: "input_text", text: "Reply with exactly PONG" }]
558
+ },
559
+ {
560
+ type: "message",
561
+ role: "user",
562
+ content: [{ type: "input_text", text: "keep" }]
563
+ },
564
+ { type: "reasoning", encrypted_content: "rsn_ok", summary: [] }
565
+ ]
566
+ });
567
+ });
526
568
  test("Bedrock mantle Responses drops Codex search_content_types before egress", async () => {
527
569
  const forwarded = [];
528
570
  const source = new BedrockProviderSource({
@@ -551,6 +593,40 @@ test("Bedrock mantle Responses drops Codex search_content_types before egress",
551
593
  }
552
594
  ]);
553
595
  });
596
+ test("Bedrock mantle Responses rewrites Codex agent_message before egress", async () => {
597
+ const forwarded = [];
598
+ const source = new BedrockProviderSource({
599
+ env: { AWS_BEARER_TOKEN_BEDROCK: "bedrock-key", AWS_REGION: "us-east-1" },
600
+ controlClient: { send: async () => ({}) },
601
+ runtimeClient: { send: async () => ({}) },
602
+ mantleBackend: {
603
+ chat: async () => Response.json({}),
604
+ responses: async (body) => {
605
+ forwarded.push(body);
606
+ return Response.json({ id: "resp" });
607
+ }
608
+ }
609
+ });
610
+ const response = await source.responses({
611
+ model: "openai.gpt-5.6-sol",
612
+ input: [{
613
+ type: "agent_message",
614
+ author: "/root",
615
+ recipient: "/root/async_lifecycle",
616
+ content: [
617
+ { type: "input_text", text: "hi" },
618
+ { type: "encrypted_content", encrypted_content: " " }
619
+ ]
620
+ }]
621
+ });
622
+ assert.equal(response.status, 200);
623
+ assert.deepEqual(forwarded, [
624
+ {
625
+ model: "openai.gpt-5.6-sol",
626
+ input: [{ type: "message", role: "user", content: [{ type: "input_text", text: "hi" }] }]
627
+ }
628
+ ]);
629
+ });
554
630
  test("Bedrock OpenAI chat without a mantle key returns 400 and does not call Converse", async () => {
555
631
  const runtimeCalls = [];
556
632
  const source = new BedrockProviderSource({
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@velum-labs/routekit-gateway",
3
3
  "private": false,
4
- "version": "0.18.4",
4
+ "version": "0.18.5",
5
5
  "repository": {
6
6
  "type": "git",
7
7
  "url": "git+https://github.com/velum-labs/routekit.git",
@@ -29,10 +29,10 @@
29
29
  "@aws-sdk/client-bedrock": "3.1095.0",
30
30
  "@aws-sdk/client-bedrock-runtime": "3.1095.0",
31
31
  "zod": "4.4.3",
32
- "@velum-labs/routekit-contracts": "0.18.4",
33
- "@velum-labs/routekit-registry": "0.18.4",
34
- "@velum-labs/routekit-runtime": "0.18.4",
35
- "@velum-labs/routekit-tracing": "0.18.4"
32
+ "@velum-labs/routekit-contracts": "0.18.5",
33
+ "@velum-labs/routekit-registry": "0.18.5",
34
+ "@velum-labs/routekit-runtime": "0.18.5",
35
+ "@velum-labs/routekit-tracing": "0.18.5"
36
36
  },
37
37
  "keywords": [
38
38
  "routekit",