@jsonstudio/llms 0.6.54 → 0.6.74
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.
- package/dist/conversion/codecs/responses-openai-codec.js +16 -1
- package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js +0 -11
- package/dist/conversion/responses/responses-openai-bridge.d.ts +1 -0
- package/dist/conversion/responses/responses-openai-bridge.js +71 -0
- package/dist/conversion/shared/tool-filter-pipeline.js +63 -21
- package/dist/conversion/shared/tool-mapping.js +52 -2
- package/dist/filters/special/request-tools-normalize.js +20 -1
- package/dist/tools/tool-registry.js +4 -3
- package/package.json +1 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildResponsesPayloadFromChat, runStandardChatRequestFilters } from '../index.js';
|
|
2
|
-
import { captureResponsesContext, buildChatRequestFromResponses } from '../responses/responses-openai-bridge.js';
|
|
2
|
+
import { captureResponsesContext, buildChatRequestFromResponses, buildResponsesRequestFromChat } from '../responses/responses-openai-bridge.js';
|
|
3
|
+
import { captureResponsesRequestContext } from '../shared/responses-conversation-store.js';
|
|
3
4
|
import { FilterEngine, ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } from '../../filters/index.js';
|
|
4
5
|
// Ported from root package (no behavior change). Types relaxed.
|
|
5
6
|
export class ResponsesOpenAIConversionCodec {
|
|
@@ -83,6 +84,20 @@ export class ResponsesOpenAIConversionCodec {
|
|
|
83
84
|
endpoint: context.endpoint ?? dto.metadata?.endpoint
|
|
84
85
|
};
|
|
85
86
|
const filtered = await runStandardChatRequestFilters(chatRequest, profile, ctxForFilters);
|
|
87
|
+
try {
|
|
88
|
+
const rebuilt = buildResponsesRequestFromChat(filtered, ctx);
|
|
89
|
+
const payloadForStore = rebuilt?.request;
|
|
90
|
+
if (payloadForStore && typeof payloadForStore === 'object') {
|
|
91
|
+
captureResponsesRequestContext({
|
|
92
|
+
requestId: dto.route.requestId,
|
|
93
|
+
payload: payloadForStore,
|
|
94
|
+
context: ctx
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
// best-effort capture
|
|
100
|
+
}
|
|
86
101
|
if (filtered && typeof filtered === 'object') {
|
|
87
102
|
const maybe = filtered;
|
|
88
103
|
if (maybe.max_tokens === undefined && typeof maybe.max_output_tokens === 'number') {
|
package/dist/conversion/hub/pipeline/stages/req_inbound/req_inbound_stage3_context_capture/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { captureResponsesContext, buildChatRequestFromResponses } from '../../../../../responses/responses-openai-bridge.js';
|
|
2
|
-
import { captureResponsesRequestContext } from '../../../../../shared/responses-conversation-store.js';
|
|
3
2
|
import { recordStage } from '../../../stages/utils.js';
|
|
4
3
|
export async function runReqInboundStage3ContextCapture(options) {
|
|
5
4
|
let context;
|
|
@@ -54,16 +53,6 @@ export function captureResponsesContextSnapshot(options) {
|
|
|
54
53
|
catch {
|
|
55
54
|
// best-effort context capture
|
|
56
55
|
}
|
|
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
|
-
}
|
|
67
56
|
return context;
|
|
68
57
|
}
|
|
69
58
|
function captureChatContextSnapshot(options) {
|
|
@@ -48,6 +48,7 @@ export declare function buildResponsesRequestFromChat(payload: Record<string, un
|
|
|
48
48
|
bridgeHistory?: BridgeInputBuildResult;
|
|
49
49
|
systemInstruction?: string;
|
|
50
50
|
}): BuildResponsesRequestResult;
|
|
51
|
+
export declare function ensureResponsesApplyPatchArguments(input?: BridgeInputItem[]): void;
|
|
51
52
|
export declare function buildResponsesPayloadFromChat(payload: unknown, context?: ResponsesRequestContext): Record<string, unknown> | unknown;
|
|
52
53
|
export declare function extractRequestIdFromResponse(response: any): string | undefined;
|
|
53
54
|
export { buildChatResponseFromResponses } from '../shared/responses-response-utils.js';
|
|
@@ -18,6 +18,7 @@ function isObject(v) {
|
|
|
18
18
|
// --- Public bridge functions ---
|
|
19
19
|
export function captureResponsesContext(payload, dto) {
|
|
20
20
|
const preservedInput = cloneBridgeEntries(payload.input);
|
|
21
|
+
ensureResponsesApplyPatchArguments(preservedInput);
|
|
21
22
|
ensureBridgeInstructions(payload);
|
|
22
23
|
const context = {
|
|
23
24
|
requestId: dto?.route?.requestId,
|
|
@@ -298,6 +299,76 @@ export function buildResponsesRequestFromChat(payload, ctx, extras) {
|
|
|
298
299
|
ensureBridgeInstructions(out);
|
|
299
300
|
return { request: out, originalSystemMessages };
|
|
300
301
|
}
|
|
302
|
+
export function ensureResponsesApplyPatchArguments(input) {
|
|
303
|
+
if (!Array.isArray(input) || !input.length) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
for (const entry of input) {
|
|
307
|
+
if (!entry || typeof entry !== 'object')
|
|
308
|
+
continue;
|
|
309
|
+
const type = typeof entry.type === 'string' ? entry.type.toLowerCase() : '';
|
|
310
|
+
if (type !== 'function_call')
|
|
311
|
+
continue;
|
|
312
|
+
const name = (typeof entry.name === 'string' && entry.name.trim()) ||
|
|
313
|
+
(entry.function && typeof entry.function === 'object' && typeof entry.function.name === 'string' && entry.function.name.trim()) ||
|
|
314
|
+
'';
|
|
315
|
+
if (name !== 'apply_patch')
|
|
316
|
+
continue;
|
|
317
|
+
let normalized;
|
|
318
|
+
try {
|
|
319
|
+
normalized = normalizeApplyPatchArguments(entry.arguments ?? entry.function?.arguments);
|
|
320
|
+
}
|
|
321
|
+
catch {
|
|
322
|
+
// best-effort: do not fail the whole request due to a malformed historical tool call
|
|
323
|
+
normalized = undefined;
|
|
324
|
+
}
|
|
325
|
+
if (normalized === undefined) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
entry.arguments = normalized;
|
|
329
|
+
if (entry.function && typeof entry.function === 'object') {
|
|
330
|
+
entry.function.arguments = normalized;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function normalizeApplyPatchArguments(source) {
|
|
335
|
+
let parsed;
|
|
336
|
+
if (typeof source === 'string' && source.trim()) {
|
|
337
|
+
try {
|
|
338
|
+
parsed = JSON.parse(source);
|
|
339
|
+
}
|
|
340
|
+
catch {
|
|
341
|
+
parsed = { patch: source };
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
else if (source && typeof source === 'object') {
|
|
345
|
+
parsed = { ...source };
|
|
346
|
+
}
|
|
347
|
+
else if (source === undefined) {
|
|
348
|
+
parsed = {};
|
|
349
|
+
}
|
|
350
|
+
else {
|
|
351
|
+
return typeof source === 'string' ? source : undefined;
|
|
352
|
+
}
|
|
353
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
354
|
+
return typeof source === 'string' ? source : undefined;
|
|
355
|
+
}
|
|
356
|
+
const patchText = typeof parsed.patch === 'string' && parsed.patch.trim().length
|
|
357
|
+
? parsed.patch
|
|
358
|
+
: typeof parsed.input === 'string' && parsed.input.trim().length
|
|
359
|
+
? parsed.input
|
|
360
|
+
: undefined;
|
|
361
|
+
if (patchText) {
|
|
362
|
+
parsed.patch = patchText;
|
|
363
|
+
parsed.input = patchText;
|
|
364
|
+
}
|
|
365
|
+
try {
|
|
366
|
+
return JSON.stringify(parsed);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return typeof source === 'string' ? source : undefined;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
301
372
|
function readToolCallIdStyleFromContext(ctx) {
|
|
302
373
|
if (!ctx) {
|
|
303
374
|
return undefined;
|
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { FilterEngine } from '../../filters/index.js';
|
|
2
2
|
import { loadFieldMapConfig } from '../../filters/utils/fieldmap-loader.js';
|
|
3
3
|
import { createSnapshotWriter } from './snapshot-utils.js';
|
|
4
|
+
const REQUEST_FILTER_STAGES = [
|
|
5
|
+
'request_pre',
|
|
6
|
+
'request_map',
|
|
7
|
+
'request_post',
|
|
8
|
+
'request_finalize'
|
|
9
|
+
];
|
|
10
|
+
const RESPONSE_FILTER_STAGES = [
|
|
11
|
+
'response_pre',
|
|
12
|
+
'response_map',
|
|
13
|
+
'response_post',
|
|
14
|
+
'response_finalize'
|
|
15
|
+
];
|
|
16
|
+
function assertStageCoverage(label, registeredStages, skeletonStages) {
|
|
17
|
+
const allowed = new Set(skeletonStages);
|
|
18
|
+
const uncovered = [];
|
|
19
|
+
for (const stage of registeredStages) {
|
|
20
|
+
if (!allowed.has(stage)) {
|
|
21
|
+
uncovered.push(stage);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
if (uncovered.length) {
|
|
25
|
+
throw new Error(`[tool-filter-pipeline] ${label}: registered filter stage(s) not covered by skeleton: ${uncovered.join(', ')}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
4
28
|
export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
5
29
|
const reqCtxBase = {
|
|
6
30
|
requestId: options.requestId ?? `req_${Date.now()}`,
|
|
@@ -23,22 +47,27 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
23
47
|
};
|
|
24
48
|
recordStage('req_process_tool_filters_input', chatRequest);
|
|
25
49
|
const engine = new FilterEngine();
|
|
50
|
+
const registeredStages = new Set();
|
|
51
|
+
const register = (filter) => {
|
|
52
|
+
registeredStages.add(filter.stage);
|
|
53
|
+
engine.registerFilter(filter);
|
|
54
|
+
};
|
|
26
55
|
const profile = (reqCtxBase.profile || '').toLowerCase();
|
|
27
56
|
const endpoint = (reqCtxBase.endpoint || '').toLowerCase();
|
|
28
57
|
const isAnthropic = profile === 'anthropic-messages' || endpoint.includes('/v1/messages');
|
|
29
58
|
if (!isAnthropic) {
|
|
30
59
|
try {
|
|
31
60
|
const { RequestToolListFilter } = await import('../../filters/index.js');
|
|
32
|
-
|
|
61
|
+
register(new RequestToolListFilter());
|
|
33
62
|
}
|
|
34
63
|
catch {
|
|
35
64
|
/* optional */
|
|
36
65
|
}
|
|
37
66
|
}
|
|
38
67
|
const { RequestToolCallsStringifyFilter, RequestToolChoicePolicyFilter } = await import('../../filters/index.js');
|
|
39
|
-
|
|
68
|
+
register(new RequestToolCallsStringifyFilter());
|
|
40
69
|
if (!isAnthropic) {
|
|
41
|
-
|
|
70
|
+
register(new RequestToolChoicePolicyFilter());
|
|
42
71
|
}
|
|
43
72
|
try {
|
|
44
73
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
@@ -52,12 +81,20 @@ export async function runChatRequestToolFilters(chatRequest, options = {}) {
|
|
|
52
81
|
} })());
|
|
53
82
|
}
|
|
54
83
|
catch { /* ignore */ }
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
84
|
+
try {
|
|
85
|
+
const { RequestOpenAIToolsNormalizeFilter, ToolPostConstraintsFilter } = await import('../../filters/index.js');
|
|
86
|
+
register(new RequestOpenAIToolsNormalizeFilter());
|
|
87
|
+
register(new ToolPostConstraintsFilter('request_finalize'));
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
// optional; keep prior behavior when filter not available
|
|
91
|
+
}
|
|
92
|
+
assertStageCoverage('request', registeredStages, REQUEST_FILTER_STAGES);
|
|
93
|
+
let staged = chatRequest;
|
|
94
|
+
for (const stage of REQUEST_FILTER_STAGES) {
|
|
95
|
+
staged = await engine.run(stage, staged, reqCtxBase);
|
|
96
|
+
recordStage(`req_process_tool_filters_${stage}`, staged);
|
|
97
|
+
}
|
|
61
98
|
recordStage('req_process_tool_filters_output', staged);
|
|
62
99
|
return staged;
|
|
63
100
|
}
|
|
@@ -81,20 +118,25 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
81
118
|
};
|
|
82
119
|
recordStage('resp_process_tool_filters_input', chatJson);
|
|
83
120
|
const engine = new FilterEngine();
|
|
121
|
+
const registeredStages = new Set();
|
|
122
|
+
const register = (filter) => {
|
|
123
|
+
registeredStages.add(filter.stage);
|
|
124
|
+
engine.registerFilter(filter);
|
|
125
|
+
};
|
|
84
126
|
const { ResponseToolTextCanonicalizeFilter, ResponseToolArgumentsStringifyFilter, ResponseFinishInvariantsFilter } = await import('../../filters/index.js');
|
|
85
|
-
|
|
127
|
+
register(new ResponseToolTextCanonicalizeFilter());
|
|
86
128
|
try {
|
|
87
129
|
const { ResponseToolArgumentsToonDecodeFilter, ResponseToolArgumentsBlacklistFilter, ResponseToolArgumentsSchemaConvergeFilter } = await import('../../filters/index.js');
|
|
88
|
-
|
|
130
|
+
register(new ResponseToolArgumentsToonDecodeFilter());
|
|
89
131
|
try {
|
|
90
|
-
|
|
132
|
+
register(new ResponseToolArgumentsSchemaConvergeFilter());
|
|
91
133
|
}
|
|
92
134
|
catch { /* optional */ }
|
|
93
|
-
|
|
135
|
+
register(new ResponseToolArgumentsBlacklistFilter());
|
|
94
136
|
}
|
|
95
137
|
catch { /* optional */ }
|
|
96
|
-
|
|
97
|
-
|
|
138
|
+
register(new ResponseToolArgumentsStringifyFilter());
|
|
139
|
+
register(new ResponseFinishInvariantsFilter());
|
|
98
140
|
try {
|
|
99
141
|
const cfg = await loadFieldMapConfig('openai-openai.fieldmap.json');
|
|
100
142
|
if (cfg)
|
|
@@ -107,12 +149,12 @@ export async function runChatResponseToolFilters(chatJson, options = {}) {
|
|
|
107
149
|
} })());
|
|
108
150
|
}
|
|
109
151
|
catch { /* ignore */ }
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
152
|
+
assertStageCoverage('response', registeredStages, RESPONSE_FILTER_STAGES);
|
|
153
|
+
let staged = chatJson;
|
|
154
|
+
for (const stage of RESPONSE_FILTER_STAGES) {
|
|
155
|
+
staged = await engine.run(stage, staged, resCtxBase);
|
|
156
|
+
recordStage(`resp_process_tool_filters_${stage}`, staged);
|
|
157
|
+
}
|
|
116
158
|
recordStage('resp_process_tool_filters_output', staged);
|
|
117
159
|
return staged;
|
|
118
160
|
}
|
|
@@ -8,6 +8,56 @@ export function stringifyArgs(args) {
|
|
|
8
8
|
return String(args);
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
|
+
function isPlainObject(value) {
|
|
12
|
+
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function clonePlainObject(value) {
|
|
15
|
+
try {
|
|
16
|
+
return JSON.parse(JSON.stringify(value));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { ...value };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function asSchema(value) {
|
|
23
|
+
if (isPlainObject(value)) {
|
|
24
|
+
return clonePlainObject(value);
|
|
25
|
+
}
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
function ensureApplyPatchSchema(seed) {
|
|
29
|
+
const schema = seed ? { ...seed } : {};
|
|
30
|
+
schema.type = typeof schema.type === 'string' ? schema.type : 'object';
|
|
31
|
+
const properties = isPlainObject(schema.properties) ? { ...schema.properties } : {};
|
|
32
|
+
properties.input = {
|
|
33
|
+
type: 'string',
|
|
34
|
+
description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
|
|
35
|
+
};
|
|
36
|
+
if (!properties.patch || typeof properties.patch !== 'object') {
|
|
37
|
+
properties.patch = {
|
|
38
|
+
type: 'string',
|
|
39
|
+
description: 'Alias of input for backwards compatibility.'
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
schema.properties = properties;
|
|
43
|
+
const requiredList = Array.isArray(schema.required) ? schema.required.filter((entry) => typeof entry === 'string') : [];
|
|
44
|
+
if (!requiredList.includes('input')) {
|
|
45
|
+
requiredList.push('input');
|
|
46
|
+
}
|
|
47
|
+
schema.required = requiredList;
|
|
48
|
+
if (typeof schema.additionalProperties !== 'boolean') {
|
|
49
|
+
schema.additionalProperties = false;
|
|
50
|
+
}
|
|
51
|
+
return schema;
|
|
52
|
+
}
|
|
53
|
+
function enforceBuiltinToolSchema(name, candidate) {
|
|
54
|
+
const normalizedName = typeof name === 'string' ? name.trim().toLowerCase() : '';
|
|
55
|
+
if (normalizedName === 'apply_patch') {
|
|
56
|
+
const base = asSchema(candidate);
|
|
57
|
+
return ensureApplyPatchSchema(base);
|
|
58
|
+
}
|
|
59
|
+
return asSchema(candidate);
|
|
60
|
+
}
|
|
11
61
|
const DEFAULT_SANITIZER = (value) => {
|
|
12
62
|
if (typeof value === 'string') {
|
|
13
63
|
const trimmed = value.trim();
|
|
@@ -66,7 +116,7 @@ export function bridgeToolToChatDefinition(rawTool, options) {
|
|
|
66
116
|
return null;
|
|
67
117
|
}
|
|
68
118
|
const description = resolveToolDescription(fnNode?.description ?? tool.description);
|
|
69
|
-
const parameters = resolveToolParameters(fnNode, tool);
|
|
119
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, tool));
|
|
70
120
|
const strict = resolveToolStrict(fnNode, tool);
|
|
71
121
|
const rawType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
72
122
|
const normalizedType = rawType.toLowerCase() === 'custom' ? 'function' : rawType;
|
|
@@ -105,7 +155,7 @@ export function chatToolToBridgeDefinition(rawTool, options) {
|
|
|
105
155
|
return null;
|
|
106
156
|
}
|
|
107
157
|
const description = resolveToolDescription(fnNode?.description);
|
|
108
|
-
const parameters = resolveToolParameters(fnNode, undefined);
|
|
158
|
+
const parameters = enforceBuiltinToolSchema(name, resolveToolParameters(fnNode, undefined));
|
|
109
159
|
const strict = resolveToolStrict(fnNode, undefined);
|
|
110
160
|
const normalizedType = typeof tool.type === 'string' && tool.type.trim().length ? tool.type.trim() : 'function';
|
|
111
161
|
const responseShape = {
|
|
@@ -73,7 +73,7 @@ export class RequestOpenAIToolsNormalizeFilter {
|
|
|
73
73
|
delete dst.function.strict;
|
|
74
74
|
}
|
|
75
75
|
catch { /* ignore */ }
|
|
76
|
-
// Switch schema for
|
|
76
|
+
// Switch schema for specific built-in tools at unified shaping point
|
|
77
77
|
try {
|
|
78
78
|
const name = String(dst.function.name || '').toLowerCase();
|
|
79
79
|
if (name === 'shell') {
|
|
@@ -101,6 +101,25 @@ export class RequestOpenAIToolsNormalizeFilter {
|
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
}
|
|
104
|
+
else if (name === 'apply_patch') {
|
|
105
|
+
dst.function.parameters = {
|
|
106
|
+
type: 'object',
|
|
107
|
+
properties: {
|
|
108
|
+
input: {
|
|
109
|
+
type: 'string',
|
|
110
|
+
description: 'Unified diff patch body between *** Begin Patch/*** End Patch.'
|
|
111
|
+
},
|
|
112
|
+
patch: {
|
|
113
|
+
type: 'string',
|
|
114
|
+
description: 'Alias of input for backwards compatibility.'
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
required: ['input'],
|
|
118
|
+
additionalProperties: false
|
|
119
|
+
};
|
|
120
|
+
dst.function.description =
|
|
121
|
+
'Use apply_patch to edit files. Provide the diff via the input field (*** Begin Patch ... *** End Patch).';
|
|
122
|
+
}
|
|
104
123
|
}
|
|
105
124
|
catch { /* ignore */ }
|
|
106
125
|
finalTools.push(dst);
|
|
@@ -95,11 +95,12 @@ export function validateToolCall(name, argsString) {
|
|
|
95
95
|
const rawArgs = tryParseJson(typeof argsString === 'string' ? argsString : '{}');
|
|
96
96
|
switch (normalizedName) {
|
|
97
97
|
case 'apply_patch': {
|
|
98
|
-
const
|
|
98
|
+
const input = asString(rawArgs.input);
|
|
99
|
+
const patch = asString(rawArgs.patch) ?? input;
|
|
99
100
|
if (!patch) {
|
|
100
|
-
return { ok: false, reason: '
|
|
101
|
+
return { ok: false, reason: 'missing_input' };
|
|
101
102
|
}
|
|
102
|
-
return { ok: true, normalizedArgs: toJson({ patch }) };
|
|
103
|
+
return { ok: true, normalizedArgs: toJson({ input: patch, patch }) };
|
|
103
104
|
}
|
|
104
105
|
case 'shell': {
|
|
105
106
|
const rawCommand = rawArgs.command;
|