@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
@@ -0,0 +1,21 @@
1
+ import type { SemanticMapper } from '../../format-adapters/index.js';
2
+ import type { AdapterContext, ChatEnvelope } from '../../types/chat-envelope.js';
3
+ import type { FormatEnvelope } from '../../types/format-envelope.js';
4
+ import { type JsonObject, type JsonValue } from '../../types/json.js';
5
+ interface ResponsesToolOutputEntry extends JsonObject {
6
+ tool_call_id?: string;
7
+ call_id?: string;
8
+ id?: string;
9
+ output?: JsonValue;
10
+ name?: string;
11
+ }
12
+ interface ResponsesPayload extends JsonObject {
13
+ input?: JsonValue[];
14
+ tools?: JsonValue[];
15
+ tool_outputs?: ResponsesToolOutputEntry[];
16
+ }
17
+ export declare class ResponsesSemanticMapper implements SemanticMapper {
18
+ toChat(format: FormatEnvelope<ResponsesPayload>, ctx: AdapterContext): Promise<ChatEnvelope>;
19
+ fromChat(chat: ChatEnvelope, ctx: AdapterContext): Promise<FormatEnvelope>;
20
+ }
21
+ export {};
@@ -0,0 +1,535 @@
1
+ import { isJsonObject, jsonClone } from '../../types/json.js';
2
+ import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../../../responses/responses-openai-bridge.js';
3
+ import { maybeAugmentApplyPatchErrorContent } from './chat-mapper.js';
4
+ const RESPONSES_PARAMETER_KEYS = [
5
+ 'model',
6
+ 'temperature',
7
+ 'top_p',
8
+ 'top_k',
9
+ 'max_tokens',
10
+ 'max_output_tokens',
11
+ 'response_format',
12
+ 'tool_choice',
13
+ 'parallel_tool_calls',
14
+ 'user',
15
+ 'logit_bias',
16
+ 'seed',
17
+ 'stop',
18
+ 'stop_sequences',
19
+ 'modalities'
20
+ ];
21
+ const RESPONSES_SUBMIT_ENDPOINT = '/v1/responses.submit_tool_outputs';
22
+ function mapToolOutputs(entries, missing) {
23
+ if (!entries || !entries.length)
24
+ return undefined;
25
+ const outputs = [];
26
+ entries.forEach((entry, index) => {
27
+ if (!isJsonObject(entry)) {
28
+ missing.push({ path: `tool_outputs[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(entry) });
29
+ return;
30
+ }
31
+ const callId = entry.tool_call_id || entry.call_id || entry.id;
32
+ if (!callId) {
33
+ missing.push({ path: `tool_outputs[${index}].tool_call_id`, reason: 'missing_tool_call_id' });
34
+ return;
35
+ }
36
+ let content = '';
37
+ if (typeof entry.output === 'string') {
38
+ content = entry.output;
39
+ }
40
+ else if (entry.output != null) {
41
+ try {
42
+ content = JSON.stringify(entry.output);
43
+ }
44
+ catch {
45
+ content = String(entry.output);
46
+ }
47
+ }
48
+ const nameValue = typeof entry.name === 'string' ? entry.name : undefined;
49
+ const augmented = maybeAugmentApplyPatchErrorContent(content, nameValue);
50
+ outputs.push({
51
+ tool_call_id: String(callId),
52
+ content: augmented,
53
+ name: nameValue
54
+ });
55
+ });
56
+ return outputs.length ? outputs : undefined;
57
+ }
58
+ function deriveResumeToolOutputs(ctx) {
59
+ if (!ctx || typeof ctx !== 'object') {
60
+ return undefined;
61
+ }
62
+ const resume = ctx.responsesResume;
63
+ if (!resume || typeof resume !== 'object') {
64
+ return undefined;
65
+ }
66
+ const detailed = Array.isArray(resume.toolOutputsDetailed)
67
+ ? resume.toolOutputsDetailed
68
+ : undefined;
69
+ if (!detailed || detailed.length === 0) {
70
+ return undefined;
71
+ }
72
+ const outputs = [];
73
+ detailed.forEach((entry, index) => {
74
+ if (!entry || typeof entry !== 'object') {
75
+ return;
76
+ }
77
+ const callIdRaw = typeof entry.callId === 'string' && entry.callId.trim().length
78
+ ? entry.callId.trim()
79
+ : typeof entry.originalId === 'string' && entry.originalId.trim().length
80
+ ? entry.originalId.trim()
81
+ : `resume_tool_${index}`;
82
+ if (!callIdRaw) {
83
+ return;
84
+ }
85
+ const outputText = typeof entry.outputText === 'string' ? entry.outputText : '';
86
+ outputs.push({
87
+ tool_call_id: callIdRaw,
88
+ content: outputText
89
+ });
90
+ });
91
+ return outputs.length ? outputs : undefined;
92
+ }
93
+ function collectParameters(payload, streamHint) {
94
+ const params = {};
95
+ for (const key of RESPONSES_PARAMETER_KEYS) {
96
+ if (payload[key] !== undefined) {
97
+ params[key] = payload[key];
98
+ }
99
+ }
100
+ if (streamHint !== undefined) {
101
+ params.stream = streamHint;
102
+ }
103
+ return Object.keys(params).length ? params : undefined;
104
+ }
105
+ function normalizeTools(rawTools, missing) {
106
+ if (!rawTools || rawTools.length === 0)
107
+ return undefined;
108
+ const tools = [];
109
+ rawTools.forEach((tool, index) => {
110
+ if (!tool || typeof tool !== 'object' || Array.isArray(tool)) {
111
+ missing.push({ path: `tools[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(tool) });
112
+ return;
113
+ }
114
+ const fn = tool.function;
115
+ if (!fn || typeof fn !== 'object' || Array.isArray(fn))
116
+ return;
117
+ const name = fn.name;
118
+ const nameValue = typeof name === 'string' ? name : undefined;
119
+ if (!nameValue)
120
+ return;
121
+ const descriptionValue = fn.description;
122
+ const parametersValue = fn.parameters;
123
+ const strictValue = fn.strict;
124
+ const definition = {
125
+ type: 'function',
126
+ function: {
127
+ name: nameValue,
128
+ description: typeof descriptionValue === 'string' ? descriptionValue : undefined,
129
+ parameters: parametersValue,
130
+ strict: typeof strictValue === 'boolean' ? strictValue : undefined
131
+ }
132
+ };
133
+ tools.push(definition);
134
+ });
135
+ return tools.length ? tools : undefined;
136
+ }
137
+ function normalizeMessages(value, missing) {
138
+ if (!Array.isArray(value)) {
139
+ if (value !== undefined) {
140
+ missing.push({ path: 'messages', reason: 'invalid_type', originalValue: jsonClone(value) });
141
+ }
142
+ else {
143
+ missing.push({ path: 'messages', reason: 'absent' });
144
+ }
145
+ return [];
146
+ }
147
+ const messages = [];
148
+ value.forEach((item, index) => {
149
+ if (!item || typeof item !== 'object' || Array.isArray(item)) {
150
+ missing.push({ path: `messages[${index}]`, reason: 'invalid_entry', originalValue: jsonClone(item) });
151
+ return;
152
+ }
153
+ messages.push(item);
154
+ });
155
+ return messages;
156
+ }
157
+ function serializeSystemContent(message) {
158
+ if (!message)
159
+ return undefined;
160
+ const content = message.content;
161
+ if (typeof content === 'string')
162
+ return content;
163
+ if (Array.isArray(content)) {
164
+ const parts = [];
165
+ content.forEach(part => {
166
+ if (typeof part === 'string') {
167
+ parts.push(part);
168
+ }
169
+ else if (part && typeof part === 'object') {
170
+ const text = part.text;
171
+ if (typeof text === 'string') {
172
+ parts.push(text);
173
+ }
174
+ }
175
+ });
176
+ return parts.join('');
177
+ }
178
+ if (content != null) {
179
+ try {
180
+ return JSON.stringify(content);
181
+ }
182
+ catch {
183
+ return String(content);
184
+ }
185
+ }
186
+ return undefined;
187
+ }
188
+ function mergeMetadata(a, b) {
189
+ if (!a && !b) {
190
+ return undefined;
191
+ }
192
+ if (!a && b) {
193
+ return jsonClone(b);
194
+ }
195
+ if (a && !b) {
196
+ return jsonClone(a);
197
+ }
198
+ const left = jsonClone(a);
199
+ const right = jsonClone(b);
200
+ return { ...left, ...right };
201
+ }
202
+ function isSubmitToolOutputsEndpoint(ctx) {
203
+ if (!ctx || typeof ctx !== 'object') {
204
+ return false;
205
+ }
206
+ const entry = typeof ctx.entryEndpoint === 'string' ? ctx.entryEndpoint.trim().toLowerCase() : '';
207
+ return entry === RESPONSES_SUBMIT_ENDPOINT;
208
+ }
209
+ function attachResponsesSemantics(existing, context, resume) {
210
+ if (!context && !resume) {
211
+ return existing;
212
+ }
213
+ const next = existing ? { ...existing } : {};
214
+ const currentNode = next.responses && isJsonObject(next.responses) ? { ...next.responses } : {};
215
+ if (context) {
216
+ currentNode.context = jsonClone(context);
217
+ }
218
+ if (resume) {
219
+ currentNode.resume = jsonClone(resume);
220
+ }
221
+ next.responses = currentNode;
222
+ return next;
223
+ }
224
+ function extractResponsesSemanticsNode(chat) {
225
+ if (!chat?.semantics || typeof chat.semantics !== 'object') {
226
+ return undefined;
227
+ }
228
+ const node = chat.semantics.responses;
229
+ return node && isJsonObject(node) ? node : undefined;
230
+ }
231
+ function readResponsesContextFromSemantics(chat) {
232
+ const node = extractResponsesSemanticsNode(chat);
233
+ if (!node) {
234
+ return undefined;
235
+ }
236
+ const contextNode = node.context;
237
+ if (!contextNode || !isJsonObject(contextNode)) {
238
+ return undefined;
239
+ }
240
+ return jsonClone(contextNode);
241
+ }
242
+ function readResponsesResumeFromSemantics(chat) {
243
+ const node = extractResponsesSemanticsNode(chat);
244
+ if (!node) {
245
+ return undefined;
246
+ }
247
+ const resumeNode = node.resume;
248
+ if (!resumeNode || !isJsonObject(resumeNode)) {
249
+ return undefined;
250
+ }
251
+ return jsonClone(resumeNode);
252
+ }
253
+ function selectResponsesContextSnapshot(chat, envelopeMetadata) {
254
+ const semanticsContext = readResponsesContextFromSemantics(chat);
255
+ const metadataContextCandidate = chat.metadata?.responsesContext;
256
+ const metadataContext = metadataContextCandidate && isJsonObject(metadataContextCandidate)
257
+ ? jsonClone(metadataContextCandidate)
258
+ : undefined;
259
+ const context = semanticsContext ??
260
+ metadataContext ??
261
+ {
262
+ metadata: envelopeMetadata
263
+ };
264
+ const mergedMetadata = mergeMetadata(context.metadata ?? undefined, envelopeMetadata);
265
+ if (mergedMetadata) {
266
+ context.metadata = mergedMetadata;
267
+ }
268
+ return context;
269
+ }
270
+ function resolveSubmitResponseId(ctx, responsesContext) {
271
+ const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
272
+ ? ctx.responsesResume
273
+ : undefined;
274
+ const resumeId = typeof resumeMeta?.restoredFromResponseId === 'string'
275
+ ? resumeMeta.restoredFromResponseId.trim()
276
+ : undefined;
277
+ if (resumeId) {
278
+ return resumeId;
279
+ }
280
+ const contextRecord = responsesContext && typeof responsesContext === 'object'
281
+ ? responsesContext
282
+ : undefined;
283
+ const previousId = typeof contextRecord?.previous_response_id === 'string'
284
+ ? contextRecord.previous_response_id.trim()
285
+ : undefined;
286
+ if (previousId) {
287
+ return previousId;
288
+ }
289
+ return undefined;
290
+ }
291
+ function extractSubmitMetadata(responsesContext, resumeMeta) {
292
+ if (responsesContext && responsesContext.metadata && isJsonObject(responsesContext.metadata)) {
293
+ return jsonClone(responsesContext.metadata);
294
+ }
295
+ if (resumeMeta && resumeMeta.metadata && isJsonObject(resumeMeta.metadata)) {
296
+ return jsonClone(resumeMeta.metadata);
297
+ }
298
+ return undefined;
299
+ }
300
+ function coerceOutputText(value) {
301
+ if (typeof value === 'string') {
302
+ return value;
303
+ }
304
+ if (value === undefined || value === null) {
305
+ return '';
306
+ }
307
+ try {
308
+ return JSON.stringify(value);
309
+ }
310
+ catch {
311
+ return String(value);
312
+ }
313
+ }
314
+ function extractCapturedToolOutputs(responsesContext) {
315
+ if (!responsesContext || typeof responsesContext !== 'object') {
316
+ return [];
317
+ }
318
+ const snapshot = responsesContext.__captured_tool_results;
319
+ if (!Array.isArray(snapshot) || !snapshot.length) {
320
+ return [];
321
+ }
322
+ const entries = [];
323
+ snapshot.forEach((entry) => {
324
+ if (!entry || typeof entry !== 'object') {
325
+ return;
326
+ }
327
+ const record = entry;
328
+ const toolId = typeof record.tool_call_id === 'string' && record.tool_call_id.trim().length
329
+ ? record.tool_call_id.trim()
330
+ : typeof record.call_id === 'string' && record.call_id.trim().length
331
+ ? record.call_id.trim()
332
+ : undefined;
333
+ if (!toolId) {
334
+ return;
335
+ }
336
+ entries.push({
337
+ tool_call_id: toolId,
338
+ id: toolId,
339
+ output: typeof record.output === 'string' ? record.output : coerceOutputText(record.output),
340
+ name: typeof record.name === 'string' ? record.name : undefined
341
+ });
342
+ });
343
+ return entries;
344
+ }
345
+ function collectSubmitToolOutputs(chat, ctx, responsesContext) {
346
+ const outputs = [];
347
+ const seen = new Set();
348
+ const append = (idSeed, outputSeed, name) => {
349
+ const trimmed = typeof idSeed === 'string' && idSeed.trim().length ? idSeed.trim() : '';
350
+ const fallbackId = trimmed || `submit_tool_${outputs.length + 1}`;
351
+ if (seen.has(fallbackId)) {
352
+ return;
353
+ }
354
+ seen.add(fallbackId);
355
+ outputs.push({
356
+ tool_call_id: fallbackId,
357
+ id: fallbackId,
358
+ output: coerceOutputText(outputSeed),
359
+ name
360
+ });
361
+ };
362
+ if (Array.isArray(chat.toolOutputs) && chat.toolOutputs.length) {
363
+ chat.toolOutputs.forEach((entry) => {
364
+ append(entry.tool_call_id ?? entry.call_id, entry.content, entry.name);
365
+ });
366
+ }
367
+ if (!outputs.length) {
368
+ const captured = extractCapturedToolOutputs(responsesContext);
369
+ captured.forEach((entry) => append(entry.tool_call_id ?? entry.id, entry.output, entry.name));
370
+ }
371
+ if (!outputs.length) {
372
+ const resume = ctx.responsesResume && typeof ctx.responsesResume === 'object'
373
+ ? ctx.responsesResume
374
+ : undefined;
375
+ const detailed = Array.isArray(resume?.toolOutputsDetailed)
376
+ ? resume?.toolOutputsDetailed
377
+ : undefined;
378
+ if (detailed) {
379
+ detailed.forEach((entry, index) => {
380
+ if (!entry || typeof entry !== 'object') {
381
+ return;
382
+ }
383
+ const callId = typeof entry.callId === 'string' && entry.callId.trim().length
384
+ ? entry.callId.trim()
385
+ : typeof entry.originalId === 'string' && entry.originalId.trim().length
386
+ ? entry.originalId.trim()
387
+ : `resume_tool_${index + 1}`;
388
+ append(callId, typeof entry.outputText === 'string' ? entry.outputText : entry.outputText ?? '');
389
+ });
390
+ }
391
+ }
392
+ return outputs;
393
+ }
394
+ function resolveSubmitStreamFlag(chat, _ctx, responsesContext) {
395
+ if (chat.parameters && typeof chat.parameters.stream === 'boolean') {
396
+ return chat.parameters.stream;
397
+ }
398
+ if (responsesContext && typeof responsesContext.stream === 'boolean') {
399
+ return responsesContext.stream;
400
+ }
401
+ return undefined;
402
+ }
403
+ function resolveSubmitModel(chat, responsesContext) {
404
+ const direct = chat.parameters && typeof chat.parameters.model === 'string'
405
+ ? chat.parameters.model.trim()
406
+ : undefined;
407
+ if (direct) {
408
+ return direct;
409
+ }
410
+ if (responsesContext && typeof responsesContext.parameters === 'object') {
411
+ const params = responsesContext.parameters;
412
+ const model = typeof params.model === 'string' ? params.model.trim() : undefined;
413
+ if (model) {
414
+ return model;
415
+ }
416
+ }
417
+ return undefined;
418
+ }
419
+ function buildSubmitToolOutputsPayload(chat, ctx, responsesContext) {
420
+ const responseId = resolveSubmitResponseId(ctx, responsesContext);
421
+ if (!responseId) {
422
+ throw new Error('Submit tool outputs requires response_id from Responses resume context');
423
+ }
424
+ const toolOutputs = collectSubmitToolOutputs(chat, ctx, responsesContext);
425
+ if (!toolOutputs.length) {
426
+ throw new Error('Submit tool outputs requires at least one tool output entry');
427
+ }
428
+ const resumeMeta = ctx.responsesResume && typeof ctx.responsesResume === 'object'
429
+ ? ctx.responsesResume
430
+ : undefined;
431
+ const payload = {
432
+ response_id: responseId,
433
+ tool_outputs: toolOutputs
434
+ };
435
+ const modelValue = resolveSubmitModel(chat, responsesContext);
436
+ if (modelValue) {
437
+ payload.model = modelValue;
438
+ }
439
+ const streamValue = resolveSubmitStreamFlag(chat, ctx, responsesContext);
440
+ if (typeof streamValue === 'boolean') {
441
+ payload.stream = streamValue;
442
+ }
443
+ const metadata = extractSubmitMetadata(responsesContext, resumeMeta);
444
+ if (metadata) {
445
+ payload.metadata = metadata;
446
+ }
447
+ return payload;
448
+ }
449
+ export class ResponsesSemanticMapper {
450
+ async toChat(format, ctx) {
451
+ const payload = format.payload || {};
452
+ const responsesContext = captureResponsesContext(payload, { route: { requestId: ctx.requestId } });
453
+ const { request, toolsNormalized } = buildChatRequestFromResponses(payload, responsesContext);
454
+ const missingFields = [];
455
+ const messages = normalizeMessages(request.messages, missingFields);
456
+ let toolOutputs = mapToolOutputs(payload.tool_outputs, missingFields);
457
+ if (!toolOutputs || toolOutputs.length === 0) {
458
+ const resumeToolOutputs = deriveResumeToolOutputs(ctx);
459
+ if (resumeToolOutputs && resumeToolOutputs.length) {
460
+ toolOutputs = resumeToolOutputs;
461
+ }
462
+ }
463
+ const parameters = collectParameters(payload, responsesContext.stream);
464
+ const metadata = { context: ctx };
465
+ if (missingFields.length) {
466
+ metadata.missingFields = missingFields;
467
+ }
468
+ if (responsesContext.responseFormat) {
469
+ metadata.responseFormat = jsonClone(responsesContext.responseFormat);
470
+ }
471
+ metadata.responsesContext = jsonClone(responsesContext);
472
+ const resumeNode = ctx.responsesResume && isJsonObject(ctx.responsesResume)
473
+ ? ctx.responsesResume
474
+ : undefined;
475
+ const semantics = attachResponsesSemantics(undefined, responsesContext, resumeNode);
476
+ return {
477
+ messages,
478
+ tools: normalizeTools(toolsNormalized, missingFields),
479
+ toolOutputs,
480
+ parameters,
481
+ semantics,
482
+ metadata
483
+ };
484
+ }
485
+ async fromChat(chat, ctx) {
486
+ const envelopeMetadata = chat.metadata && isJsonObject(chat.metadata) ? chat.metadata : undefined;
487
+ const responsesContext = selectResponsesContextSnapshot(chat, envelopeMetadata);
488
+ if (isSubmitToolOutputsEndpoint(ctx)) {
489
+ const submitPayload = buildSubmitToolOutputsPayload(chat, ctx, responsesContext);
490
+ return {
491
+ protocol: 'openai-responses',
492
+ direction: 'response',
493
+ payload: submitPayload,
494
+ meta: {
495
+ context: ctx,
496
+ submitToolOutputs: true
497
+ }
498
+ };
499
+ }
500
+ const modelValue = chat.parameters?.model;
501
+ if (typeof modelValue !== 'string' || !modelValue.trim()) {
502
+ throw new Error('ChatEnvelope.parameters.model is required for openai-responses outbound conversion');
503
+ }
504
+ const requestShape = {
505
+ ...(chat.parameters || {}),
506
+ model: modelValue,
507
+ messages: chat.messages,
508
+ tools: chat.tools
509
+ };
510
+ const originalSystemMessages = (chat.messages || [])
511
+ .filter((message) => Boolean(message && typeof message === 'object' && message.role === 'system'))
512
+ .map(message => serializeSystemContent(message))
513
+ .filter((content) => typeof content === 'string' && content.length > 0);
514
+ responsesContext.originalSystemMessages = originalSystemMessages;
515
+ const responsesResult = buildResponsesRequestFromChat(requestShape, responsesContext);
516
+ const responses = responsesResult.request;
517
+ if (chat.parameters && chat.parameters.stream !== undefined) {
518
+ responses.stream = chat.parameters.stream;
519
+ }
520
+ // Do not forward ChatEnvelope.toolOutputs to OpenAI Responses create requests.
521
+ // Upstream expects historical tool results to remain inside input[] as
522
+ // tool role messages; sending the legacy top-level `tool_outputs` field
523
+ // causes providers like FAI to reject the request (HTTP 400). Any actual
524
+ // submit_tool_outputs call should be issued via the dedicated endpoint
525
+ // upstream, not through this mapper.
526
+ return {
527
+ protocol: 'openai-responses',
528
+ direction: 'response',
529
+ payload: responses,
530
+ meta: {
531
+ context: ctx
532
+ }
533
+ };
534
+ }
535
+ }
@@ -0,0 +1,19 @@
1
+ import type { StandardizedRequest, StandardizedTool } from '../types/standardized.js';
2
+ export type HubOperation = {
3
+ op: 'set_request_metadata_fields';
4
+ fields: Record<string, unknown>;
5
+ } | {
6
+ op: 'unset_request_metadata_keys';
7
+ keys: string[];
8
+ } | {
9
+ op: 'set_request_parameter_fields';
10
+ fields: Record<string, unknown>;
11
+ } | {
12
+ op: 'unset_request_parameter_keys';
13
+ keys: string[];
14
+ } | {
15
+ op: 'append_tool_if_missing';
16
+ toolName: string;
17
+ tool: StandardizedTool;
18
+ };
19
+ export declare function applyHubOperations<T extends StandardizedRequest>(request: T, ops: HubOperation[]): T;
@@ -0,0 +1,126 @@
1
+ function normalizeKeys(value) {
2
+ if (!Array.isArray(value)) {
3
+ return [];
4
+ }
5
+ const out = [];
6
+ for (const entry of value) {
7
+ if (typeof entry !== 'string')
8
+ continue;
9
+ const trimmed = entry.trim();
10
+ if (!trimmed)
11
+ continue;
12
+ out.push(trimmed);
13
+ }
14
+ return out;
15
+ }
16
+ function hasToolByName(tools, name) {
17
+ const n = typeof name === 'string' ? name.trim() : '';
18
+ if (!n)
19
+ return false;
20
+ if (!Array.isArray(tools) || tools.length === 0)
21
+ return false;
22
+ return tools.some((tool) => {
23
+ if (!tool || typeof tool !== 'object')
24
+ return false;
25
+ const fn = tool.function;
26
+ return typeof fn?.name === 'string' && fn.name.trim() === n;
27
+ });
28
+ }
29
+ export function applyHubOperations(request, ops) {
30
+ if (!ops || ops.length === 0) {
31
+ return request;
32
+ }
33
+ let next = request;
34
+ for (const op of ops) {
35
+ if (!op || typeof op !== 'object')
36
+ continue;
37
+ if (op.op === 'set_request_metadata_fields') {
38
+ const patch = op.fields && typeof op.fields === 'object' && !Array.isArray(op.fields) ? op.fields : {};
39
+ if (Object.keys(patch).length === 0) {
40
+ continue;
41
+ }
42
+ next = {
43
+ ...next,
44
+ metadata: {
45
+ ...(next.metadata ?? {}),
46
+ ...patch
47
+ }
48
+ };
49
+ continue;
50
+ }
51
+ if (op.op === 'unset_request_metadata_keys') {
52
+ const keys = normalizeKeys(op.keys);
53
+ if (keys.length === 0) {
54
+ continue;
55
+ }
56
+ const base = next.metadata && typeof next.metadata === 'object' ? { ...next.metadata } : {};
57
+ let changed = false;
58
+ for (const key of keys) {
59
+ if (key in base) {
60
+ delete base[key];
61
+ changed = true;
62
+ }
63
+ }
64
+ if (!changed) {
65
+ continue;
66
+ }
67
+ next = {
68
+ ...next,
69
+ metadata: base
70
+ };
71
+ continue;
72
+ }
73
+ if (op.op === 'set_request_parameter_fields') {
74
+ const patch = op.fields && typeof op.fields === 'object' && !Array.isArray(op.fields) ? op.fields : {};
75
+ if (Object.keys(patch).length === 0) {
76
+ continue;
77
+ }
78
+ next = {
79
+ ...next,
80
+ parameters: {
81
+ ...(next.parameters ?? {}),
82
+ ...patch
83
+ }
84
+ };
85
+ continue;
86
+ }
87
+ if (op.op === 'unset_request_parameter_keys') {
88
+ const keys = normalizeKeys(op.keys);
89
+ if (keys.length === 0) {
90
+ continue;
91
+ }
92
+ const base = next.parameters && typeof next.parameters === 'object' ? { ...next.parameters } : {};
93
+ let changed = false;
94
+ for (const key of keys) {
95
+ if (key in base) {
96
+ delete base[key];
97
+ changed = true;
98
+ }
99
+ }
100
+ if (!changed) {
101
+ continue;
102
+ }
103
+ next = {
104
+ ...next,
105
+ parameters: base
106
+ };
107
+ continue;
108
+ }
109
+ if (op.op === 'append_tool_if_missing') {
110
+ const toolName = typeof op.toolName === 'string' ? op.toolName.trim() : '';
111
+ if (!toolName) {
112
+ continue;
113
+ }
114
+ if (hasToolByName(next.tools, toolName)) {
115
+ continue;
116
+ }
117
+ const existingTools = Array.isArray(next.tools) ? next.tools : [];
118
+ next = {
119
+ ...next,
120
+ tools: [...existingTools, op.tool]
121
+ };
122
+ continue;
123
+ }
124
+ }
125
+ return next;
126
+ }