@jsonstudio/llms 0.6.938 → 0.6.1164

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.
Files changed (131) hide show
  1. package/dist/conversion/hub/operation-table/operation-table-runner.d.ts +18 -0
  2. package/dist/conversion/hub/operation-table/operation-table-runner.js +158 -0
  3. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.d.ts +8 -0
  4. package/dist/conversion/hub/operation-table/semantic-mappers/anthropic-mapper.js +303 -0
  5. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.d.ts +8 -0
  6. package/dist/conversion/hub/operation-table/semantic-mappers/chat-mapper.js +413 -0
  7. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.d.ts +7 -0
  8. package/dist/conversion/hub/operation-table/semantic-mappers/gemini-mapper.js +841 -0
  9. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.d.ts +21 -0
  10. package/dist/conversion/hub/operation-table/semantic-mappers/responses-mapper.js +535 -0
  11. package/dist/conversion/hub/ops/operations.d.ts +19 -0
  12. package/dist/conversion/hub/ops/operations.js +126 -0
  13. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +9 -0
  14. package/dist/conversion/hub/pipeline/hub-pipeline.js +533 -24
  15. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage2_semantic_map/index.js +6 -0
  16. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +6 -3
  17. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage1_semantic_map/index.js +11 -0
  18. package/dist/conversion/hub/policy/policy-engine.js +41 -9
  19. package/dist/conversion/hub/policy/protocol-spec.d.ts +25 -0
  20. package/dist/conversion/hub/policy/protocol-spec.js +73 -23
  21. package/dist/conversion/hub/process/chat-process.js +252 -41
  22. package/dist/conversion/hub/response/provider-response.js +175 -2
  23. package/dist/conversion/hub/response/response-runtime.js +1 -1
  24. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.d.ts +1 -8
  25. package/dist/conversion/hub/semantic-mappers/anthropic-mapper.js +1 -365
  26. package/dist/conversion/hub/semantic-mappers/chat-mapper.d.ts +1 -8
  27. package/dist/conversion/hub/semantic-mappers/chat-mapper.js +1 -436
  28. package/dist/conversion/hub/semantic-mappers/gemini-mapper.d.ts +1 -7
  29. package/dist/conversion/hub/semantic-mappers/gemini-mapper.js +1 -894
  30. package/dist/conversion/hub/semantic-mappers/responses-mapper.d.ts +1 -21
  31. package/dist/conversion/hub/semantic-mappers/responses-mapper.js +1 -593
  32. package/dist/conversion/hub/tool-surface/tool-surface-engine.d.ts +18 -0
  33. package/dist/conversion/hub/tool-surface/tool-surface-engine.js +571 -0
  34. package/dist/conversion/responses/responses-openai-bridge.js +14 -2
  35. package/dist/conversion/shared/bridge-message-utils.js +2 -8
  36. package/dist/conversion/shared/bridge-policies.js +5 -105
  37. package/dist/conversion/shared/gemini-tool-utils.js +121 -4
  38. package/dist/conversion/shared/protocol-field-allowlists.d.ts +7 -0
  39. package/dist/conversion/shared/protocol-field-allowlists.js +145 -0
  40. package/dist/conversion/shared/reasoning-tool-normalizer.js +4 -2
  41. package/dist/conversion/shared/snapshot-hooks.js +166 -3
  42. package/dist/conversion/shared/text-markup-normalizer.d.ts +2 -0
  43. package/dist/conversion/shared/text-markup-normalizer.js +345 -9
  44. package/dist/conversion/shared/thought-signature-validator.d.ts +52 -0
  45. package/dist/conversion/shared/thought-signature-validator.js +170 -0
  46. package/dist/conversion/shared/tool-argument-repairer.d.ts +39 -0
  47. package/dist/conversion/shared/tool-argument-repairer.js +56 -0
  48. package/dist/conversion/shared/tool-call-id-manager.d.ts +113 -0
  49. package/dist/conversion/shared/tool-call-id-manager.js +231 -0
  50. package/dist/conversion/shared/tool-canonicalizer.js +2 -11
  51. package/dist/router/virtual-router/bootstrap.js +54 -5
  52. package/dist/router/virtual-router/engine-selection.js +132 -42
  53. package/dist/router/virtual-router/engine.d.ts +3 -0
  54. package/dist/router/virtual-router/engine.js +142 -33
  55. package/dist/router/virtual-router/health-weighted.d.ts +25 -0
  56. package/dist/router/virtual-router/health-weighted.js +63 -0
  57. package/dist/router/virtual-router/load-balancer.d.ts +2 -0
  58. package/dist/router/virtual-router/load-balancer.js +45 -16
  59. package/dist/router/virtual-router/routing-instructions.js +17 -1
  60. package/dist/router/virtual-router/sticky-session-store.js +136 -24
  61. package/dist/router/virtual-router/stop-message-file-resolver.d.ts +1 -0
  62. package/dist/router/virtual-router/stop-message-file-resolver.js +74 -0
  63. package/dist/router/virtual-router/stop-message-state-sync.d.ts +15 -0
  64. package/dist/router/virtual-router/stop-message-state-sync.js +57 -0
  65. package/dist/router/virtual-router/types.d.ts +70 -0
  66. package/dist/servertool/clock/config.d.ts +7 -0
  67. package/dist/servertool/clock/config.js +27 -0
  68. package/dist/servertool/clock/daemon.d.ts +3 -0
  69. package/dist/servertool/clock/daemon.js +79 -0
  70. package/dist/servertool/clock/io.d.ts +2 -0
  71. package/dist/servertool/clock/io.js +13 -0
  72. package/dist/servertool/clock/paths.d.ts +4 -0
  73. package/dist/servertool/clock/paths.js +25 -0
  74. package/dist/servertool/clock/session-store.d.ts +3 -0
  75. package/dist/servertool/clock/session-store.js +56 -0
  76. package/dist/servertool/clock/state.d.ts +5 -0
  77. package/dist/servertool/clock/state.js +62 -0
  78. package/dist/servertool/clock/task-store.d.ts +5 -0
  79. package/dist/servertool/clock/task-store.js +4 -0
  80. package/dist/servertool/clock/tasks.d.ts +17 -0
  81. package/dist/servertool/clock/tasks.js +221 -0
  82. package/dist/servertool/clock/types.d.ts +36 -0
  83. package/dist/servertool/clock/types.js +1 -0
  84. package/dist/servertool/engine.d.ts +2 -0
  85. package/dist/servertool/engine.js +164 -8
  86. package/dist/servertool/followup-shadow.d.ts +16 -0
  87. package/dist/servertool/followup-shadow.js +145 -0
  88. package/dist/servertool/handlers/apply-patch-guard.js +1 -265
  89. package/dist/servertool/handlers/clock-auto.d.ts +1 -0
  90. package/dist/servertool/handlers/clock-auto.js +160 -0
  91. package/dist/servertool/handlers/clock.d.ts +1 -0
  92. package/dist/servertool/handlers/clock.js +197 -0
  93. package/dist/servertool/handlers/exec-command-guard.js +7 -555
  94. package/dist/servertool/handlers/followup-request-builder.d.ts +15 -7
  95. package/dist/servertool/handlers/followup-request-builder.js +248 -28
  96. package/dist/servertool/handlers/gemini-empty-reply-continue.js +62 -169
  97. package/dist/servertool/handlers/iflow-model-error-retry.js +18 -28
  98. package/dist/servertool/handlers/recursive-detection-guard.d.ts +1 -0
  99. package/dist/servertool/handlers/recursive-detection-guard.js +333 -0
  100. package/dist/servertool/handlers/stop-message-auto.js +47 -175
  101. package/dist/servertool/handlers/vision.d.ts +7 -1
  102. package/dist/servertool/handlers/vision.js +61 -117
  103. package/dist/servertool/handlers/web-search.d.ts +7 -1
  104. package/dist/servertool/handlers/web-search.js +122 -105
  105. package/dist/servertool/reenter-backend.d.ts +23 -0
  106. package/dist/servertool/reenter-backend.js +18 -0
  107. package/dist/servertool/server-side-tools.d.ts +3 -2
  108. package/dist/servertool/server-side-tools.js +64 -10
  109. package/dist/servertool/types.d.ts +92 -3
  110. package/dist/sse/json-to-sse/event-generators/responses.js +3 -21
  111. package/dist/sse/shared/serializers/responses-event-serializer.d.ts +8 -0
  112. package/dist/sse/shared/serializers/responses-event-serializer.js +19 -0
  113. package/dist/sse/shared/writer.js +24 -7
  114. package/dist/tools/apply-patch/execution-capturer.js +3 -1
  115. package/dist/tools/apply-patch/json/parse-loose.d.ts +3 -0
  116. package/dist/tools/apply-patch/json/parse-loose.js +139 -0
  117. package/dist/tools/apply-patch/patch-text/context-diff.d.ts +1 -0
  118. package/dist/tools/apply-patch/patch-text/context-diff.js +173 -0
  119. package/dist/tools/apply-patch/patch-text/git-diff.d.ts +1 -0
  120. package/dist/tools/apply-patch/patch-text/git-diff.js +138 -0
  121. package/dist/tools/apply-patch/patch-text/looks-like-patch.d.ts +1 -0
  122. package/dist/tools/apply-patch/patch-text/looks-like-patch.js +13 -0
  123. package/dist/tools/apply-patch/patch-text/normalize.d.ts +3 -0
  124. package/dist/tools/apply-patch/patch-text/normalize.js +262 -0
  125. package/dist/tools/apply-patch/structured/coercion.d.ts +3 -0
  126. package/dist/tools/apply-patch/structured/coercion.js +82 -0
  127. package/dist/tools/apply-patch/validation/shared.d.ts +3 -0
  128. package/dist/tools/apply-patch/validation/shared.js +6 -0
  129. package/dist/tools/apply-patch/validator.d.ts +2 -2
  130. package/dist/tools/apply-patch/validator.js +6 -556
  131. package/package.json +1 -1
@@ -1,8 +1,14 @@
1
1
  import { chatEnvelopeToStandardized } from '../../../../standardized-bridge.js';
2
2
  import { validateChatEnvelope } from '../../../../../shared/chat-envelope-validator.js';
3
+ import { applyHubOperationTableInbound } from '../../../../operation-table/operation-table-runner.js';
3
4
  import { recordStage } from '../../../stages/utils.js';
4
5
  export async function runReqInboundStage2SemanticMap(options) {
5
6
  const chatEnvelope = await options.semanticMapper.toChat(options.formatEnvelope, options.adapterContext);
7
+ applyHubOperationTableInbound({
8
+ formatEnvelope: options.formatEnvelope,
9
+ chatEnvelope,
10
+ adapterContext: options.adapterContext
11
+ });
6
12
  validateChatEnvelope(chatEnvelope, {
7
13
  stage: 'req_inbound',
8
14
  direction: 'request'
@@ -312,9 +312,6 @@ function buildApplyPatchDiagnostics(output) {
312
312
  }
313
313
  function appendDiagnosticsToRecord(record) {
314
314
  const name = typeof record.name === 'string' ? record.name.trim() : undefined;
315
- if (name !== 'apply_patch') {
316
- return;
317
- }
318
315
  let text;
319
316
  if (typeof record.output === 'string') {
320
317
  text = record.output;
@@ -329,6 +326,12 @@ function appendDiagnosticsToRecord(record) {
329
326
  if (!diag) {
330
327
  return;
331
328
  }
329
+ // Some providers / compatibility layers omit `name` on tool outputs.
330
+ // When the output text matches apply_patch argument-parse failures, still inject the diagnostics
331
+ // to keep user-visible behavior stable across modes.
332
+ if (name && name !== 'apply_patch') {
333
+ return;
334
+ }
332
335
  const merged = `${text}${diag}`;
333
336
  if (typeof record.output === 'string') {
334
337
  record.output = merged;
@@ -1,5 +1,6 @@
1
1
  import { standardizedToChatEnvelope } from '../../../../standardized-bridge.js';
2
2
  import { validateChatEnvelope } from '../../../../../shared/chat-envelope-validator.js';
3
+ import { applyHubOperationTableOutboundPostMap, applyHubOperationTableOutboundPreMap } from '../../../../operation-table/operation-table-runner.js';
3
4
  import { recordStage } from '../../../stages/utils.js';
4
5
  export async function runReqOutboundStage1SemanticMap(options) {
5
6
  const chatEnvelope = standardizedToChatEnvelope(options.request, {
@@ -16,7 +17,17 @@ export async function runReqOutboundStage1SemanticMap(options) {
16
17
  stage: 'req_outbound',
17
18
  direction: 'request'
18
19
  });
20
+ await applyHubOperationTableOutboundPreMap({
21
+ protocol: options.adapterContext.providerProtocol,
22
+ chatEnvelope,
23
+ adapterContext: options.adapterContext
24
+ });
19
25
  const formatEnvelope = (await options.semanticMapper.fromChat(chatEnvelope, options.adapterContext));
26
+ applyHubOperationTableOutboundPostMap({
27
+ chatEnvelope,
28
+ formatEnvelope,
29
+ adapterContext: options.adapterContext
30
+ });
20
31
  recordStage(options.stageRecorder, 'req_outbound_stage1_semantic_map', chatEnvelope);
21
32
  return { chatEnvelope, formatEnvelope };
22
33
  }
@@ -38,6 +38,11 @@ function applyProviderOutboundPolicy(providerProtocol, payload) {
38
38
  out = { ...payload };
39
39
  }
40
40
  };
41
+ const allowedTopLevelKeys = Array.isArray(spec.providerOutbound.allowedTopLevelKeys) &&
42
+ spec.providerOutbound.allowedTopLevelKeys.length > 0 &&
43
+ spec.providerOutbound.enforceAllowedTopLevelKeys === true
44
+ ? new Set(spec.providerOutbound.allowedTopLevelKeys)
45
+ : undefined;
41
46
  // Reserved/private keys must never be sent upstream.
42
47
  for (const key of Object.keys(payload)) {
43
48
  if (spec.providerOutbound.reservedKeyPrefixes.some((prefix) => key.startsWith(prefix))) {
@@ -76,6 +81,18 @@ function applyProviderOutboundPolicy(providerProtocol, payload) {
76
81
  delete out[wrapperKey];
77
82
  flattenedWrappers.push(wrapperKey);
78
83
  }
84
+ // Enforce protocol allowlist (top-level). Only runs when explicitly enabled
85
+ // for this protocol, and only after wrapper flatten so allowed fields are
86
+ // present at the correct level.
87
+ if (allowedTopLevelKeys) {
88
+ for (const key of Object.keys(out)) {
89
+ if (allowedTopLevelKeys.has(key))
90
+ continue;
91
+ ensureOutClone();
92
+ delete out[key];
93
+ removedTopLevelKeys.push(key);
94
+ }
95
+ }
79
96
  return {
80
97
  payload: out,
81
98
  changed: out !== payload || removedTopLevelKeys.length > 0 || flattenedWrappers.length > 0,
@@ -83,16 +100,20 @@ function applyProviderOutboundPolicy(providerProtocol, payload) {
83
100
  flattenedWrappers
84
101
  };
85
102
  }
86
- function observeProviderOutboundPayload(providerProtocol, payload) {
103
+ function observeProviderPayload(options) {
87
104
  const violations = [];
88
- // V0 (observe-only): detect known "layout anti-patterns" and reserved keys.
105
+ // Observe-only: detect known layout anti-patterns and reserved keys.
89
106
  // Do NOT modify payload here.
90
- const spec = resolveHubProtocolSpec(providerProtocol);
107
+ const spec = resolveHubProtocolSpec(options.providerProtocol);
108
+ const allowlistEnabled = options.phase === 'provider_outbound';
109
+ const allowedTopLevelKeys = allowlistEnabled && Array.isArray(spec.providerOutbound.allowedTopLevelKeys)
110
+ ? new Set(spec.providerOutbound.allowedTopLevelKeys)
111
+ : undefined;
91
112
  for (const rule of spec.providerOutbound.forbidWrappers) {
92
113
  if (rule.code !== 'forbid_wrapper') {
93
114
  continue;
94
115
  }
95
- if (rule.path in payload && isJsonRecord(payload[rule.path])) {
116
+ if (rule.path in options.payload && isJsonRecord(options.payload[rule.path])) {
96
117
  violations.push({
97
118
  code: 'unexpected_wrapper',
98
119
  path: rule.path,
@@ -101,18 +122,26 @@ function observeProviderOutboundPayload(providerProtocol, payload) {
101
122
  }
102
123
  }
103
124
  // Always record unknown private wrapper keys (best-effort, conservative).
104
- for (const key of Object.keys(payload)) {
125
+ for (const key of Object.keys(options.payload)) {
105
126
  if (spec.providerOutbound.reservedKeyPrefixes.some((prefix) => key.startsWith(prefix))) {
106
127
  violations.push({
107
128
  code: 'unexpected_field',
108
129
  path: key
109
130
  });
131
+ continue;
132
+ }
133
+ if (allowedTopLevelKeys && !allowedTopLevelKeys.has(key)) {
134
+ violations.push({
135
+ code: 'unexpected_field',
136
+ path: key,
137
+ detail: `Top-level key is not in protocol allowlist: ${options.providerProtocol}`
138
+ });
110
139
  }
111
140
  }
112
141
  const unexpectedFieldCount = violations.filter((v) => v.code === 'unexpected_field').length;
113
142
  return {
114
- phase: 'provider_outbound',
115
- providerProtocol,
143
+ phase: options.phase,
144
+ providerProtocol: options.providerProtocol,
116
145
  violations,
117
146
  summary: {
118
147
  totalViolations: violations.length,
@@ -136,8 +165,11 @@ export function recordHubPolicyObservation(options) {
136
165
  }
137
166
  try {
138
167
  const phase = options.phase ?? 'provider_outbound';
139
- const observation = observeProviderOutboundPayload(options.providerProtocol, options.payload);
140
- observation.phase = phase;
168
+ const observation = observeProviderPayload({
169
+ phase,
170
+ providerProtocol: options.providerProtocol,
171
+ payload: options.payload
172
+ });
141
173
  if (observation.summary.totalViolations <= 0) {
142
174
  return;
143
175
  }
@@ -26,6 +26,19 @@ export interface ProviderOutboundPolicySpec {
26
26
  * Keep this false for protocols not yet migrated, to avoid behavior changes.
27
27
  */
28
28
  enforceEnabled: boolean;
29
+ /**
30
+ * Provider outbound payload allowlist (top-level keys), used for observation
31
+ * to detect drift.
32
+ */
33
+ allowedTopLevelKeys?: readonly string[];
34
+ /**
35
+ * When enabled, provider outbound payload will drop any top-level keys not
36
+ * present in allowedTopLevelKeys (after wrapper flatten).
37
+ *
38
+ * Keep this configurable for progressive rollout, but Phase 1 completion
39
+ * requires enabling it for all protocols.
40
+ */
41
+ enforceAllowedTopLevelKeys?: boolean;
29
42
  /**
30
43
  * Reserved/private key prefixes that must not be sent upstream.
31
44
  * (Enforced only when enforceEnabled=true.)
@@ -42,9 +55,21 @@ export interface ProviderOutboundPolicySpec {
42
55
  */
43
56
  flattenWrappers: ProviderOutboundWrapperFlattenRule[];
44
57
  }
58
+ export type ToolDefinitionFormat = 'openai' | 'anthropic' | 'gemini';
59
+ export type ProviderOutboundHistoryCarrier = 'messages' | 'input';
60
+ export interface ToolSurfaceSpec {
61
+ expectedToolFormat: ToolDefinitionFormat;
62
+ /**
63
+ * For OpenAI protocols, tool call/result history may be carried in either
64
+ * chat `messages[]` or responses `input[]`. This spec describes the expected
65
+ * carrier so toolSurface can normalize or at least record diffs.
66
+ */
67
+ expectedHistoryCarrier?: ProviderOutboundHistoryCarrier;
68
+ }
45
69
  export interface ProtocolSpec {
46
70
  id: HubProviderProtocol;
47
71
  providerOutbound: ProviderOutboundPolicySpec;
72
+ toolSurface: ToolSurfaceSpec;
48
73
  }
49
74
  export declare const HUB_PROTOCOL_SPECS: Record<HubProviderProtocol, ProtocolSpec>;
50
75
  export declare function resolveHubProtocolSpec(protocol: string): ProtocolSpec;
@@ -1,7 +1,10 @@
1
+ import { ANTHROPIC_ALLOWED_FIELDS, ANTHROPIC_PARAMETERS_WRAPPER_ALLOW_KEYS, GEMINI_ALLOWED_FIELDS, OPENAI_CHAT_ALLOWED_FIELDS, OPENAI_CHAT_PARAMETERS_WRAPPER_ALLOW_KEYS, OPENAI_RESPONSES_ALLOWED_FIELDS, OPENAI_RESPONSES_PARAMETERS_WRAPPER_ALLOW_KEYS } from '../../shared/protocol-field-allowlists.js';
1
2
  const RESPONSES_SPEC = {
2
3
  id: 'openai-responses',
3
4
  providerOutbound: {
4
5
  enforceEnabled: true,
6
+ allowedTopLevelKeys: OPENAI_RESPONSES_ALLOWED_FIELDS,
7
+ enforceAllowedTopLevelKeys: true,
5
8
  forbidWrappers: [
6
9
  {
7
10
  code: 'forbid_wrapper',
@@ -26,30 +29,21 @@ const RESPONSES_SPEC = {
26
29
  aliasKeys: {
27
30
  max_tokens: 'max_output_tokens'
28
31
  },
29
- allowKeys: [
30
- 'temperature',
31
- 'top_p',
32
- 'max_output_tokens',
33
- 'seed',
34
- 'logit_bias',
35
- 'user',
36
- 'parallel_tool_calls',
37
- 'tool_choice',
38
- 'response_format',
39
- 'stream',
40
- 'stop',
41
- 'stop_sequences',
42
- 'modalities',
43
- 'top_k'
44
- ]
32
+ allowKeys: [...OPENAI_RESPONSES_PARAMETERS_WRAPPER_ALLOW_KEYS]
45
33
  }
46
34
  ]
35
+ },
36
+ toolSurface: {
37
+ expectedToolFormat: 'openai',
38
+ expectedHistoryCarrier: 'input'
47
39
  }
48
40
  };
49
41
  const DEFAULT_SPEC = {
50
42
  id: 'openai-chat',
51
43
  providerOutbound: {
52
- enforceEnabled: false,
44
+ enforceEnabled: true,
45
+ allowedTopLevelKeys: OPENAI_CHAT_ALLOWED_FIELDS,
46
+ enforceAllowedTopLevelKeys: true,
53
47
  forbidWrappers: [
54
48
  {
55
49
  code: 'forbid_wrapper',
@@ -63,7 +57,24 @@ const DEFAULT_SPEC = {
63
57
  }
64
58
  ],
65
59
  reservedKeyPrefixes: ['__', '_'],
66
- flattenWrappers: []
60
+ flattenWrappers: [
61
+ {
62
+ wrapperKey: 'request',
63
+ onlyIfTargetMissing: true
64
+ },
65
+ {
66
+ wrapperKey: 'parameters',
67
+ onlyIfTargetMissing: true,
68
+ aliasKeys: {
69
+ max_output_tokens: 'max_tokens'
70
+ },
71
+ allowKeys: [...OPENAI_CHAT_PARAMETERS_WRAPPER_ALLOW_KEYS]
72
+ }
73
+ ]
74
+ },
75
+ toolSurface: {
76
+ expectedToolFormat: 'openai',
77
+ expectedHistoryCarrier: 'messages'
67
78
  }
68
79
  };
69
80
  export const HUB_PROTOCOL_SPECS = {
@@ -72,7 +83,9 @@ export const HUB_PROTOCOL_SPECS = {
72
83
  'anthropic-messages': {
73
84
  id: 'anthropic-messages',
74
85
  providerOutbound: {
75
- enforceEnabled: false,
86
+ enforceEnabled: true,
87
+ allowedTopLevelKeys: ANTHROPIC_ALLOWED_FIELDS,
88
+ enforceAllowedTopLevelKeys: true,
76
89
  forbidWrappers: [
77
90
  {
78
91
  code: 'forbid_wrapper',
@@ -86,16 +99,53 @@ export const HUB_PROTOCOL_SPECS = {
86
99
  }
87
100
  ],
88
101
  reservedKeyPrefixes: ['__', '_'],
89
- flattenWrappers: []
102
+ flattenWrappers: [
103
+ {
104
+ wrapperKey: 'request',
105
+ onlyIfTargetMissing: true
106
+ },
107
+ {
108
+ wrapperKey: 'parameters',
109
+ onlyIfTargetMissing: true,
110
+ aliasKeys: {
111
+ max_output_tokens: 'max_tokens'
112
+ },
113
+ allowKeys: [...ANTHROPIC_PARAMETERS_WRAPPER_ALLOW_KEYS]
114
+ }
115
+ ]
116
+ },
117
+ toolSurface: {
118
+ expectedToolFormat: 'anthropic'
90
119
  }
91
120
  },
92
121
  'gemini-chat': {
93
122
  id: 'gemini-chat',
94
123
  providerOutbound: {
95
- enforceEnabled: false,
96
- forbidWrappers: [],
124
+ enforceEnabled: true,
125
+ allowedTopLevelKeys: GEMINI_ALLOWED_FIELDS,
126
+ enforceAllowedTopLevelKeys: true,
127
+ forbidWrappers: [
128
+ {
129
+ code: 'forbid_wrapper',
130
+ path: 'parameters',
131
+ detail: 'Gemini provider payload must not contain a top-level parameters wrapper.'
132
+ },
133
+ {
134
+ code: 'forbid_wrapper',
135
+ path: 'request',
136
+ detail: 'Gemini provider payload must not contain a nested request wrapper.'
137
+ }
138
+ ],
97
139
  reservedKeyPrefixes: ['__', '_'],
98
- flattenWrappers: []
140
+ flattenWrappers: [
141
+ {
142
+ wrapperKey: 'request',
143
+ onlyIfTargetMissing: true
144
+ }
145
+ ]
146
+ },
147
+ toolSurface: {
148
+ expectedToolFormat: 'gemini'
99
149
  }
100
150
  }
101
151
  };