@jsonstudio/llms 0.6.34 → 0.6.54

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 (38) hide show
  1. package/dist/conversion/codecs/gemini-openai-codec.js +1 -2
  2. package/dist/conversion/compat/profiles/chat-glm.json +17 -0
  3. package/dist/conversion/compat/profiles/chat-iflow.json +36 -0
  4. package/dist/conversion/compat/profiles/chat-lmstudio.json +37 -0
  5. package/dist/conversion/compat/profiles/chat-qwen.json +18 -0
  6. package/dist/conversion/compat/profiles/responses-c4m.json +45 -0
  7. package/dist/conversion/config/compat-profiles.json +38 -0
  8. package/dist/conversion/config/sample-config.json +314 -0
  9. package/dist/conversion/config/version-switch.json +150 -0
  10. package/dist/conversion/hub/pipeline/compat/compat-engine.d.ts +4 -0
  11. package/dist/conversion/hub/pipeline/compat/compat-engine.js +667 -0
  12. package/dist/conversion/hub/pipeline/compat/compat-profile-store.d.ts +2 -0
  13. package/dist/conversion/hub/pipeline/compat/compat-profile-store.js +76 -0
  14. package/dist/conversion/hub/pipeline/compat/compat-types.d.ts +62 -0
  15. package/dist/conversion/hub/pipeline/compat/compat-types.js +1 -0
  16. package/dist/conversion/hub/pipeline/hub-pipeline.d.ts +1 -0
  17. package/dist/conversion/hub/pipeline/hub-pipeline.js +76 -28
  18. package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +10 -12
  19. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.d.ts +14 -0
  20. package/dist/conversion/hub/pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js +23 -0
  21. package/dist/conversion/hub/response/provider-response.js +18 -0
  22. package/dist/conversion/hub/response/response-mappers.d.ts +1 -1
  23. package/dist/conversion/hub/response/response-mappers.js +2 -12
  24. package/dist/conversion/shared/responses-output-builder.js +22 -43
  25. package/dist/conversion/shared/responses-response-utils.js +1 -47
  26. package/dist/conversion/shared/text-markup-normalizer.js +2 -2
  27. package/dist/conversion/shared/tool-canonicalizer.js +16 -118
  28. package/dist/conversion/shared/tool-mapping.js +0 -30
  29. package/dist/filters/config/openai-openai.fieldmap.json +18 -0
  30. package/dist/index.d.ts +0 -1
  31. package/dist/index.js +0 -1
  32. package/dist/router/virtual-router/bootstrap.js +18 -33
  33. package/dist/router/virtual-router/classifier.js +51 -77
  34. package/dist/router/virtual-router/features.js +338 -111
  35. package/dist/router/virtual-router/types.d.ts +2 -4
  36. package/dist/router/virtual-router/types.js +2 -2
  37. package/dist/sse/sse-to-json/builders/response-builder.js +1 -0
  38. package/package.json +3 -3
@@ -0,0 +1,76 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import os from 'node:os';
4
+ import { fileURLToPath } from 'node:url';
5
+ const builtinDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../../compat/profiles');
6
+ const USER_COMPAT_DIR = (process.env.ROUTECODEX_COMPAT_DIR && process.env.ROUTECODEX_COMPAT_DIR.trim()) ||
7
+ path.join(os.homedir(), '.routecodex', 'compat');
8
+ let profileMap = null;
9
+ function normalizeProfiles(profiles) {
10
+ const map = new Map();
11
+ for (const profile of profiles) {
12
+ if (!profile || typeof profile !== 'object') {
13
+ continue;
14
+ }
15
+ const id = typeof profile.id === 'string' ? profile.id.trim() : '';
16
+ if (!id) {
17
+ continue;
18
+ }
19
+ map.set(id, {
20
+ ...profile,
21
+ id
22
+ });
23
+ }
24
+ return map;
25
+ }
26
+ function loadProfilesFromDir(dir) {
27
+ try {
28
+ if (!fs.existsSync(dir)) {
29
+ return [];
30
+ }
31
+ const entries = fs.readdirSync(dir);
32
+ const configs = [];
33
+ for (const entry of entries) {
34
+ if (!entry.endsWith('.json')) {
35
+ continue;
36
+ }
37
+ const file = path.join(dir, entry);
38
+ try {
39
+ const text = fs.readFileSync(file, 'utf8');
40
+ const json = JSON.parse(text);
41
+ if (!json.id) {
42
+ json.id = entry.replace(/\.json$/i, '');
43
+ }
44
+ configs.push(json);
45
+ }
46
+ catch (error) {
47
+ console.warn(`[compat] Failed to load ${file}: ${error instanceof Error ? error.message : String(error)}`);
48
+ }
49
+ }
50
+ return configs;
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ }
56
+ function buildProfileMap() {
57
+ const merged = new Map();
58
+ const builtinProfiles = normalizeProfiles(loadProfilesFromDir(builtinDir));
59
+ for (const [key, value] of builtinProfiles.entries()) {
60
+ merged.set(key, value);
61
+ }
62
+ const userProfiles = normalizeProfiles(loadProfilesFromDir(USER_COMPAT_DIR));
63
+ for (const [key, value] of userProfiles.entries()) {
64
+ merged.set(key, value);
65
+ }
66
+ return merged;
67
+ }
68
+ export function getCompatProfile(profileId) {
69
+ if (!profileId || !profileId.trim()) {
70
+ return null;
71
+ }
72
+ if (!profileMap) {
73
+ profileMap = buildProfileMap();
74
+ }
75
+ return profileMap.get(profileId.trim()) ?? null;
76
+ }
@@ -0,0 +1,62 @@
1
+ import type { JsonObject, JsonValue } from '../../types/json.js';
2
+ export type CompatDirection = 'request' | 'response';
3
+ export interface CompatProfileConfig {
4
+ id: string;
5
+ protocol: string;
6
+ direction?: CompatDirection;
7
+ mappings?: MappingInstruction[];
8
+ filters?: FilterInstruction[];
9
+ request?: CompatStageConfig;
10
+ response?: CompatStageConfig;
11
+ }
12
+ export interface CompatStageConfig {
13
+ mappings?: MappingInstruction[];
14
+ filters?: FilterInstruction[];
15
+ }
16
+ export type MappingInstruction = {
17
+ action: 'remove';
18
+ path: string;
19
+ } | {
20
+ action: 'rename';
21
+ from: string;
22
+ to: string;
23
+ } | {
24
+ action: 'set';
25
+ path: string;
26
+ value: JsonValue;
27
+ } | {
28
+ action: 'stringify';
29
+ path: string;
30
+ fallback?: JsonValue;
31
+ } | {
32
+ action: 'set_default';
33
+ path: string;
34
+ value?: JsonValue;
35
+ valueSource?: 'timestamp_seconds' | 'chat_completion_id';
36
+ } | {
37
+ action: 'normalize_tool_choice';
38
+ path?: string;
39
+ objectReplacement?: string;
40
+ } | {
41
+ action: 'inject_instruction';
42
+ sourcePath: string;
43
+ targetPath?: string;
44
+ role?: string;
45
+ contentType?: string;
46
+ stripHtml?: boolean;
47
+ maxLengthEnv?: string[];
48
+ } | {
49
+ action: 'parse_json';
50
+ path: string;
51
+ fallback?: JsonValue;
52
+ } | {
53
+ action: 'convert_responses_output_to_choices';
54
+ };
55
+ export type FilterInstruction = {
56
+ action: 'rate_limit_text';
57
+ needle: string;
58
+ };
59
+ export interface CompatApplicationResult {
60
+ payload: JsonObject;
61
+ appliedProfile?: string;
62
+ }
@@ -55,6 +55,7 @@ export declare class HubPipeline {
55
55
  private buildAdapterContext;
56
56
  private maybeCreateStageRecorder;
57
57
  private asJsonObject;
58
+ private pickRawRequestBody;
58
59
  private normalizeRequest;
59
60
  private convertProcessNodeResult;
60
61
  private materializePayload;
@@ -18,6 +18,7 @@ import { runReqProcessStage1ToolGovernance } from './stages/req_process/req_proc
18
18
  import { runReqProcessStage2RouteSelect } from './stages/req_process/req_process_stage2_route_select/index.js';
19
19
  import { runReqOutboundStage1SemanticMap } from './stages/req_outbound/req_outbound_stage1_semantic_map/index.js';
20
20
  import { runReqOutboundStage2FormatBuild } from './stages/req_outbound/req_outbound_stage2_format_build/index.js';
21
+ import { runReqOutboundStage3Compat } from './stages/req_outbound/req_outbound_stage3_compat/index.js';
21
22
  export class HubPipeline {
22
23
  routerEngine;
23
24
  config;
@@ -109,6 +110,9 @@ export class HubPipeline {
109
110
  stageRecorder: inboundRecorder
110
111
  });
111
112
  const outboundAdapterContext = this.buildAdapterContext(normalized, routing.target);
113
+ if (routing.target?.compatibilityProfile) {
114
+ outboundAdapterContext.compatibilityProfile = routing.target.compatibilityProfile;
115
+ }
112
116
  const outboundProtocol = outboundAdapterContext.providerProtocol;
113
117
  const protocolSwitch = outboundProtocol !== normalized.providerProtocol;
114
118
  const outboundHooks = protocolSwitch ? this.resolveProtocolHooks(outboundProtocol) : hooks;
@@ -122,35 +126,66 @@ export class HubPipeline {
122
126
  const outboundEndpoint = resolveEndpointForProviderProtocol(outboundAdapterContext.providerProtocol);
123
127
  const outboundRecorder = this.maybeCreateStageRecorder(outboundAdapterContext, outboundEndpoint);
124
128
  const outboundStart = Date.now();
125
- const outboundStage1 = await runReqOutboundStage1SemanticMap({
126
- request: workingRequest,
127
- adapterContext: outboundAdapterContext,
128
- semanticMapper: outboundSemanticMapper,
129
- contextSnapshot: outboundContextSnapshot,
130
- contextMetadataKey: outboundContextMetadataKey,
131
- stageRecorder: outboundRecorder
132
- });
133
- const providerPayload = await runReqOutboundStage2FormatBuild({
134
- formatEnvelope: outboundStage1.formatEnvelope,
135
- adapterContext: outboundAdapterContext,
136
- formatAdapter: outboundFormatAdapter,
137
- stageRecorder: outboundRecorder
138
- });
139
- const outboundEnd = Date.now();
140
- nodeResults.push({
141
- id: 'req_outbound',
142
- success: true,
143
- metadata: {
144
- node: 'req_outbound',
145
- executionTime: outboundEnd - outboundStart,
146
- startTime: outboundStart,
147
- endTime: outboundEnd,
148
- dataProcessed: {
149
- messages: workingRequest.messages.length,
150
- tools: workingRequest.tools?.length ?? 0
129
+ const isResponsesSubmit = normalized.entryEndpoint === '/v1/responses.submit_tool_outputs' &&
130
+ outboundProtocol === 'openai-responses';
131
+ let providerPayload;
132
+ if (isResponsesSubmit) {
133
+ providerPayload =
134
+ this.pickRawRequestBody(normalized.metadata) ?? normalized.payload;
135
+ const outboundEnd = Date.now();
136
+ nodeResults.push({
137
+ id: 'req_outbound',
138
+ success: true,
139
+ metadata: {
140
+ node: 'req_outbound',
141
+ executionTime: outboundEnd - outboundStart,
142
+ startTime: outboundStart,
143
+ endTime: outboundEnd,
144
+ dataProcessed: {
145
+ messages: 0,
146
+ tools: 0
147
+ },
148
+ bypass: 'responses-submit-passthrough'
151
149
  }
152
- }
153
- });
150
+ });
151
+ }
152
+ else {
153
+ const outboundStage1 = await runReqOutboundStage1SemanticMap({
154
+ request: workingRequest,
155
+ adapterContext: outboundAdapterContext,
156
+ semanticMapper: outboundSemanticMapper,
157
+ contextSnapshot: outboundContextSnapshot,
158
+ contextMetadataKey: outboundContextMetadataKey,
159
+ stageRecorder: outboundRecorder
160
+ });
161
+ let formattedPayload = await runReqOutboundStage2FormatBuild({
162
+ formatEnvelope: outboundStage1.formatEnvelope,
163
+ adapterContext: outboundAdapterContext,
164
+ formatAdapter: outboundFormatAdapter,
165
+ stageRecorder: outboundRecorder
166
+ });
167
+ formattedPayload = await runReqOutboundStage3Compat({
168
+ payload: formattedPayload,
169
+ adapterContext: outboundAdapterContext,
170
+ stageRecorder: outboundRecorder
171
+ });
172
+ providerPayload = formattedPayload;
173
+ const outboundEnd = Date.now();
174
+ nodeResults.push({
175
+ id: 'req_outbound',
176
+ success: true,
177
+ metadata: {
178
+ node: 'req_outbound',
179
+ executionTime: outboundEnd - outboundStart,
180
+ startTime: outboundStart,
181
+ endTime: outboundEnd,
182
+ dataProcessed: {
183
+ messages: workingRequest.messages.length,
184
+ tools: workingRequest.tools?.length ?? 0
185
+ }
186
+ }
187
+ });
188
+ }
154
189
  const metadata = {
155
190
  ...normalized.metadata,
156
191
  entryEndpoint: normalized.entryEndpoint,
@@ -271,6 +306,9 @@ export class HubPipeline {
271
306
  if (typeof metadata.assignedModelId === 'string') {
272
307
  adapterContext.modelId = metadata.assignedModelId;
273
308
  }
309
+ if (target?.compatibilityProfile && typeof target.compatibilityProfile === 'string') {
310
+ adapterContext.compatibilityProfile = target.compatibilityProfile;
311
+ }
274
312
  return adapterContext;
275
313
  }
276
314
  maybeCreateStageRecorder(context, endpoint) {
@@ -292,6 +330,16 @@ export class HubPipeline {
292
330
  }
293
331
  return value;
294
332
  }
333
+ pickRawRequestBody(metadata) {
334
+ if (!metadata || typeof metadata !== 'object') {
335
+ return undefined;
336
+ }
337
+ const raw = metadata.__raw_request_body;
338
+ if (!raw || typeof raw !== 'object') {
339
+ return undefined;
340
+ }
341
+ return raw;
342
+ }
295
343
  async normalizeRequest(request) {
296
344
  if (!request || typeof request !== 'object') {
297
345
  throw new Error('HubPipeline requires request payload');
@@ -19,18 +19,6 @@ export async function runReqInboundStage3ContextCapture(options) {
19
19
  ? augmentContextSnapshot(context, fallbackSnapshot)
20
20
  : fallbackSnapshot;
21
21
  recordStage(options.stageRecorder, 'req_inbound_stage3_context_capture', snapshot);
22
- if (options.adapterContext.providerProtocol === 'openai-responses') {
23
- try {
24
- captureResponsesRequestContext({
25
- requestId: options.adapterContext.requestId,
26
- payload: options.rawRequest,
27
- context: snapshot
28
- });
29
- }
30
- catch {
31
- /* ignore capture failures */
32
- }
33
- }
34
22
  return context ?? snapshot;
35
23
  }
36
24
  export function runChatContextCapture(options) {
@@ -66,6 +54,16 @@ export function captureResponsesContextSnapshot(options) {
66
54
  catch {
67
55
  // best-effort context capture
68
56
  }
57
+ try {
58
+ captureResponsesRequestContext({
59
+ requestId: options.adapterContext.requestId,
60
+ payload: options.rawRequest,
61
+ context
62
+ });
63
+ }
64
+ catch {
65
+ // ignore store capture failures
66
+ }
69
67
  return context;
70
68
  }
71
69
  function captureChatContextSnapshot(options) {
@@ -0,0 +1,14 @@
1
+ import type { AdapterContext } from '../../../../types/chat-envelope.js';
2
+ import type { StageRecorder } from '../../../../format-adapters/index.js';
3
+ import type { JsonObject } from '../../../../types/json.js';
4
+ export type ProviderPayload = JsonObject;
5
+ export declare function runReqOutboundStage3Compat(options: {
6
+ payload: ProviderPayload;
7
+ adapterContext: AdapterContext;
8
+ stageRecorder?: StageRecorder;
9
+ }): Promise<ProviderPayload>;
10
+ export declare function runRespInboundStageCompatResponse(options: {
11
+ payload: ProviderPayload;
12
+ adapterContext: AdapterContext;
13
+ stageRecorder?: StageRecorder;
14
+ }): ProviderPayload;
@@ -0,0 +1,23 @@
1
+ import { applyRequestCompat, applyResponseCompat } from '../../../compat/compat-engine.js';
2
+ function pickCompatProfile(adapterContext) {
3
+ const candidate = adapterContext.compatibilityProfile;
4
+ return typeof candidate === 'string' && candidate.trim() ? candidate.trim() : undefined;
5
+ }
6
+ export async function runReqOutboundStage3Compat(options) {
7
+ const profile = pickCompatProfile(options.adapterContext);
8
+ const result = applyRequestCompat(profile, options.payload);
9
+ options.stageRecorder?.record('req_outbound_stage3_compat', {
10
+ applied: Boolean(result.appliedProfile),
11
+ profile: result.appliedProfile || profile || 'passthrough'
12
+ });
13
+ return result.payload;
14
+ }
15
+ export function runRespInboundStageCompatResponse(options) {
16
+ const profile = pickCompatProfile(options.adapterContext);
17
+ const result = applyResponseCompat(profile, options.payload);
18
+ options.stageRecorder?.record('resp_inbound_stage_compat', {
19
+ applied: Boolean(result.appliedProfile),
20
+ profile: result.appliedProfile || profile || 'passthrough'
21
+ });
22
+ return result.payload;
23
+ }
@@ -6,10 +6,12 @@ import { OpenAIChatResponseMapper, ResponsesResponseMapper, AnthropicResponseMap
6
6
  import { runRespInboundStage1SseDecode } from '../pipeline/stages/resp_inbound/resp_inbound_stage1_sse_decode/index.js';
7
7
  import { runRespInboundStage2FormatParse } from '../pipeline/stages/resp_inbound/resp_inbound_stage2_format_parse/index.js';
8
8
  import { runRespInboundStage3SemanticMap } from '../pipeline/stages/resp_inbound/resp_inbound_stage3_semantic_map/index.js';
9
+ import { runRespInboundStageCompatResponse } from '../pipeline/stages/req_outbound/req_outbound_stage3_compat/index.js';
9
10
  import { runRespProcessStage1ToolGovernance } from '../pipeline/stages/resp_process/resp_process_stage1_tool_governance/index.js';
10
11
  import { runRespProcessStage2Finalize } from '../pipeline/stages/resp_process/resp_process_stage2_finalize/index.js';
11
12
  import { runRespOutboundStage1ClientRemap } from '../pipeline/stages/resp_outbound/resp_outbound_stage1_client_remap/index.js';
12
13
  import { runRespOutboundStage2SseStream } from '../pipeline/stages/resp_outbound/resp_outbound_stage2_sse_stream/index.js';
14
+ import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
13
15
  function resolveChatReasoningMode(entryEndpoint) {
14
16
  const envRaw = (process.env.ROUTECODEX_CHAT_REASONING_MODE || process.env.RCC_CHAT_REASONING_MODE || '').trim().toLowerCase();
15
17
  const map = {
@@ -105,6 +107,22 @@ export async function convertProviderResponse(options) {
105
107
  formatAdapter,
106
108
  stageRecorder: options.stageRecorder
107
109
  });
110
+ if (options.providerProtocol === 'openai-responses') {
111
+ try {
112
+ recordResponsesResponse({
113
+ requestId: options.context.requestId,
114
+ response: formatEnvelope.payload
115
+ });
116
+ }
117
+ catch {
118
+ // ignore conversation capture errors
119
+ }
120
+ }
121
+ formatEnvelope.payload = runRespInboundStageCompatResponse({
122
+ payload: formatEnvelope.payload,
123
+ adapterContext: options.context,
124
+ stageRecorder: options.stageRecorder
125
+ });
108
126
  const chatResponse = await runRespInboundStage3SemanticMap({
109
127
  adapterContext: options.context,
110
128
  formatEnvelope,
@@ -9,7 +9,7 @@ export declare class OpenAIChatResponseMapper implements ResponseMapper {
9
9
  toChatCompletion(format: FormatEnvelope, _ctx: AdapterContext): ChatCompletionLike;
10
10
  }
11
11
  export declare class ResponsesResponseMapper implements ResponseMapper {
12
- toChatCompletion(format: FormatEnvelope, ctx: AdapterContext): ChatCompletionLike;
12
+ toChatCompletion(format: FormatEnvelope, _ctx: AdapterContext): ChatCompletionLike;
13
13
  }
14
14
  export declare class AnthropicResponseMapper implements ResponseMapper {
15
15
  toChatCompletion(format: FormatEnvelope, ctx: AdapterContext): ChatCompletionLike;
@@ -1,6 +1,5 @@
1
1
  import { buildOpenAIChatFromGeminiResponse } from '../../codecs/gemini-openai-codec.js';
2
2
  import { buildChatResponseFromResponses } from '../../shared/responses-response-utils.js';
3
- import { recordResponsesResponse } from '../../shared/responses-conversation-store.js';
4
3
  import { buildOpenAIChatFromAnthropicMessage } from './response-runtime.js';
5
4
  export class OpenAIChatResponseMapper {
6
5
  toChatCompletion(format, _ctx) {
@@ -8,17 +7,8 @@ export class OpenAIChatResponseMapper {
8
7
  }
9
8
  }
10
9
  export class ResponsesResponseMapper {
11
- toChatCompletion(format, ctx) {
12
- const payload = (format.payload ?? {});
13
- if (ctx?.requestId) {
14
- try {
15
- recordResponsesResponse({ requestId: ctx.requestId, response: payload });
16
- }
17
- catch {
18
- /* ignore capture failures */
19
- }
20
- }
21
- return buildChatResponseFromResponses(payload);
10
+ toChatCompletion(format, _ctx) {
11
+ return buildChatResponseFromResponses(format.payload ?? {});
22
12
  }
23
13
  }
24
14
  export class AnthropicResponseMapper {
@@ -112,50 +112,29 @@ export function buildResponsesOutputFromChat(options) {
112
112
  };
113
113
  }
114
114
  function normalizeUsage(usageRaw) {
115
- if (!usageRaw || typeof usageRaw !== 'object') {
116
- return undefined;
117
- }
118
- const source = usageRaw;
119
- const usage = {};
120
- const inputValue = normalizeNumber(source.input_tokens ?? source.prompt_tokens);
121
- if (inputValue !== undefined) {
122
- usage.input_tokens = inputValue;
123
- }
124
- const outputValue = normalizeNumber(source.output_tokens ?? source.completion_tokens);
125
- if (outputValue !== undefined) {
126
- usage.output_tokens = outputValue;
127
- }
128
- const totalValue = normalizeNumber(source.total_tokens);
129
- if (totalValue !== undefined) {
130
- usage.total_tokens = totalValue;
131
- }
132
- else if (inputValue !== undefined && outputValue !== undefined) {
133
- usage.total_tokens = inputValue + outputValue;
134
- }
135
- const inputDetails = extractTokenDetails(source.input_tokens_details ??
136
- source.prompt_tokens_details);
137
- if (inputDetails) {
138
- usage.input_tokens_details = inputDetails;
139
- }
140
- const outputDetails = extractTokenDetails(source.output_tokens_details ??
141
- source.completion_tokens_details);
142
- if (outputDetails) {
143
- usage.output_tokens_details = outputDetails;
144
- }
145
- return Object.keys(usage).length ? usage : undefined;
146
- }
147
- function normalizeNumber(value) {
148
- if (value == null) {
149
- return undefined;
150
- }
151
- const parsed = Number(value);
152
- return Number.isFinite(parsed) ? parsed : undefined;
153
- }
154
- function extractTokenDetails(raw) {
155
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
156
- return undefined;
115
+ if (usageRaw && typeof usageRaw === 'object') {
116
+ const usage = { ...usageRaw };
117
+ if (usage.input_tokens != null && usage.prompt_tokens == null) {
118
+ usage.prompt_tokens = usage.input_tokens;
119
+ }
120
+ if (usage.output_tokens != null && usage.completion_tokens == null) {
121
+ usage.completion_tokens = usage.output_tokens;
122
+ }
123
+ if (usage.prompt_tokens != null && usage.completion_tokens != null && usage.total_tokens == null) {
124
+ const total = Number(usage.prompt_tokens) + Number(usage.completion_tokens);
125
+ if (!Number.isNaN(total))
126
+ usage.total_tokens = total;
127
+ }
128
+ try {
129
+ delete usage.input_tokens;
130
+ delete usage.output_tokens;
131
+ }
132
+ catch {
133
+ /* ignore */
134
+ }
135
+ return usage;
157
136
  }
158
- return { ...raw };
137
+ return usageRaw;
159
138
  }
160
139
  function buildFunctionCallOutput(call, allocateOutputId, sanitizeFunctionName, baseCount, offset) {
161
140
  try {
@@ -167,7 +167,7 @@ export function buildChatResponseFromResponses(payload) {
167
167
  const created = typeof response.created_at === 'number'
168
168
  ? response.created_at
169
169
  : (response.created ?? Math.floor(Date.now() / 1000));
170
- const usage = mapResponsesUsageToChat(response.usage);
170
+ const usage = response.usage;
171
171
  const toolCalls = collectToolCallsFromResponses(response);
172
172
  const { textParts, reasoningParts } = extractOutputSegments(response);
173
173
  const rawReasoningSegments = collectRawReasoningSegments(response);
@@ -237,49 +237,3 @@ export function buildChatResponseFromResponses(payload) {
237
237
  }
238
238
  return chat;
239
239
  }
240
- function mapResponsesUsageToChat(usageRaw) {
241
- if (!usageRaw || typeof usageRaw !== 'object') {
242
- return undefined;
243
- }
244
- const source = usageRaw;
245
- const usage = {};
246
- const promptValue = toNumber(source.prompt_tokens ?? source.input_tokens);
247
- if (promptValue !== undefined) {
248
- usage.prompt_tokens = promptValue;
249
- }
250
- const completionValue = toNumber(source.completion_tokens ?? source.output_tokens);
251
- if (completionValue !== undefined) {
252
- usage.completion_tokens = completionValue;
253
- }
254
- const totalValue = toNumber(source.total_tokens);
255
- if (totalValue !== undefined) {
256
- usage.total_tokens = totalValue;
257
- }
258
- else if (promptValue !== undefined && completionValue !== undefined) {
259
- usage.total_tokens = promptValue + completionValue;
260
- }
261
- const promptDetails = extractDetailObject(source.prompt_tokens_details ??
262
- source.input_tokens_details);
263
- if (promptDetails) {
264
- usage.prompt_tokens_details = promptDetails;
265
- }
266
- const completionDetails = extractDetailObject(source.completion_tokens_details ??
267
- source.output_tokens_details);
268
- if (completionDetails) {
269
- usage.completion_tokens_details = completionDetails;
270
- }
271
- return Object.keys(usage).length ? usage : undefined;
272
- }
273
- function toNumber(value) {
274
- if (value == null) {
275
- return undefined;
276
- }
277
- const parsed = Number(value);
278
- return Number.isFinite(parsed) ? parsed : undefined;
279
- }
280
- function extractDetailObject(raw) {
281
- if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
282
- return undefined;
283
- }
284
- return { ...raw };
285
- }
@@ -95,10 +95,10 @@ export function extractApplyPatchCallsFromText(text) {
95
95
  continue;
96
96
  let argsStr = '{}';
97
97
  try {
98
- argsStr = JSON.stringify({ input: patch });
98
+ argsStr = JSON.stringify({ patch });
99
99
  }
100
100
  catch {
101
- argsStr = '{"input":""}';
101
+ argsStr = '{"patch":""}';
102
102
  }
103
103
  out.push({ id: genId(), name: 'apply_patch', args: argsStr });
104
104
  }