Skip to content

Commit f1f2ccd

Browse files
authored
feat(tarko-agent-cli): support unknown options passthrough (#1574)
1 parent 77a2a27 commit f1f2ccd

File tree

3 files changed

+262
-8
lines changed

3 files changed

+262
-8
lines changed

multimodal/tarko/agent-cli/src/config/builder.ts

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ export function buildAppConfig<
5656
config = deepMerge(config, workspaceConfig);
5757
}
5858

59-
// Extract CLI-specific properties
59+
// Extract known CLI options, everything else (including unknown options) goes to cliConfigProps
6060
const {
6161
agent,
6262
workspace,
@@ -65,35 +65,37 @@ export function buildAppConfig<
6565
quiet,
6666
port,
6767
stream,
68-
// Extract core deprecated options
68+
headless,
69+
input,
70+
format,
71+
includeLogs,
72+
useCache,
73+
open,
6974
provider,
7075
apiKey,
7176
baseURL,
7277
shareProvider,
7378
thinking,
74-
// Extract tool filter options
7579
tool,
76-
// Extract MCP server filter options
7780
mcpServer,
78-
// Extract server options
7981
server,
8082
...cliConfigProps
8183
} = cliArguments;
8284

8385
// Handle deprecated options
84-
const deprecatedOptions = {
86+
const deprecatedOptionValues = {
8587
provider,
8688
apiKey: apiKey || undefined,
8789
baseURL,
8890
shareProvider,
8991
thinking,
9092
}; // secretlint-disable-line @secretlint/secretlint-rule-pattern
91-
const deprecatedKeys = Object.entries(deprecatedOptions)
93+
const deprecatedKeys = Object.entries(deprecatedOptionValues)
9294
.filter(([, value]) => value !== undefined)
9395
.map(([optionName]) => optionName);
9496

9597
logDeprecatedWarning(deprecatedKeys);
96-
handleCoreDeprecatedOptions(cliConfigProps, deprecatedOptions);
98+
handleCoreDeprecatedOptions(cliConfigProps, deprecatedOptionValues);
9799

98100
// Handle tool filters
99101
handleToolFilterOptions(cliConfigProps, { tool });

multimodal/tarko/agent-cli/src/core/cli.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,9 @@ export class AgentCLI {
150150
// Apply agent-specific configurations for commands that run agents
151151
configuredCommand = this.configureAgentCommand(configuredCommand);
152152

153+
// Allow unknown options to be passed through to agents
154+
configuredCommand.allowUnknownOptions();
155+
153156
configuredCommand.action(async (cliArguments: AgentCLIArguments = {}) => {
154157
this.printLogo();
155158

@@ -203,6 +206,10 @@ export class AgentCLI {
203206

204207
// Apply agent-specific configurations for commands that run agents
205208
configuredCommand = this.configureAgentCommand(configuredCommand);
209+
210+
// Allow unknown options to be passed through to agents
211+
configuredCommand.allowUnknownOptions();
212+
206213
configuredCommand.action(async (...args: any[]) => {
207214
// Handle dynamic arguments due to optional positional parameters [run] [agent]
208215
// CAC passes arguments in this pattern:

multimodal/tarko/agent-cli/tests/config-builder.test.ts

Lines changed: 245 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,4 +804,249 @@ describe('buildAppConfig', () => {
804804
expect(result.model?.provider).toBe('openai');
805805
});
806806
});
807+
808+
describe('unknown options passthrough', () => {
809+
it('should preserve unknown CLI options in the final config', () => {
810+
const cliArgs: AgentCLIArguments = {
811+
model: {
812+
provider: 'openai',
813+
id: 'gpt-4',
814+
},
815+
// Unknown options that should be preserved
816+
aioSandbox: 'test-sandbox-value',
817+
customOption: 'custom-value',
818+
nestedUnknown: {
819+
nested: 'value',
820+
},
821+
};
822+
823+
const result = buildAppConfig(cliArgs, {});
824+
825+
// Known options should work as expected
826+
expect(result.model).toEqual({
827+
provider: 'openai',
828+
id: 'gpt-4',
829+
});
830+
831+
// Unknown options should be preserved
832+
expect(result).toHaveProperty('aioSandbox', 'test-sandbox-value');
833+
expect(result).toHaveProperty('customOption', 'custom-value');
834+
expect(result).toHaveProperty('nestedUnknown', {
835+
nested: 'value',
836+
});
837+
});
838+
839+
it('should preserve unknown options while filtering out known CLI-only options', () => {
840+
const cliArgs: AgentCLIArguments = {
841+
// Known CLI-only options that should be filtered out
842+
agent: 'agent-tars',
843+
workspace: '/workspace',
844+
debug: true,
845+
quiet: false,
846+
headless: true,
847+
input: 'test input',
848+
format: 'json',
849+
includeLogs: true,
850+
useCache: false,
851+
open: true,
852+
853+
// Known options that should be preserved
854+
model: {
855+
provider: 'openai',
856+
},
857+
858+
// Unknown options that should be preserved
859+
aioSandbox: 'sandbox-value',
860+
customAgentOption: 'agent-specific-value',
861+
};
862+
863+
const result = buildAppConfig(cliArgs, {});
864+
865+
// Known CLI-only options should not appear in result
866+
expect(result).not.toHaveProperty('agent');
867+
expect(result).not.toHaveProperty('workspace');
868+
expect(result).not.toHaveProperty('debug');
869+
expect(result).not.toHaveProperty('quiet');
870+
expect(result).not.toHaveProperty('headless');
871+
expect(result).not.toHaveProperty('input');
872+
expect(result).not.toHaveProperty('format');
873+
expect(result).not.toHaveProperty('includeLogs');
874+
expect(result).not.toHaveProperty('useCache');
875+
expect(result).not.toHaveProperty('open');
876+
877+
// Known options should be preserved
878+
expect(result.model).toEqual({
879+
provider: 'openai',
880+
});
881+
882+
// Unknown options should be preserved
883+
expect(result).toHaveProperty('aioSandbox', 'sandbox-value');
884+
expect(result).toHaveProperty('customAgentOption', 'agent-specific-value');
885+
});
886+
887+
it('should preserve unknown options alongside deprecated options', () => {
888+
const cliArgs: AgentCLIArguments = {
889+
// Deprecated options
890+
provider: 'openai',
891+
apiKey: 'deprecated-key', // secretlint-disable-line
892+
893+
// Unknown options
894+
aioSandbox: 'test-value',
895+
customFeature: true,
896+
};
897+
898+
const result = buildAppConfig(cliArgs, {});
899+
900+
// Deprecated options should be handled normally
901+
expect(result.model).toEqual({
902+
provider: 'openai',
903+
apiKey: 'deprecated-key', // secretlint-disable-line
904+
});
905+
906+
// Unknown options should be preserved
907+
expect(result).toHaveProperty('aioSandbox', 'test-value');
908+
expect(result).toHaveProperty('customFeature', true);
909+
});
910+
911+
it('should handle unknown options with complex data types', () => {
912+
const complexObject = {
913+
nested: {
914+
array: [1, 2, 3],
915+
boolean: true,
916+
string: 'test',
917+
},
918+
};
919+
920+
const cliArgs: AgentCLIArguments = {
921+
model: {
922+
provider: 'openai',
923+
},
924+
complexUnknownOption: complexObject,
925+
arrayOption: ['item1', 'item2'],
926+
numberOption: 42,
927+
booleanOption: false,
928+
};
929+
930+
const result = buildAppConfig(cliArgs, {});
931+
932+
expect(result).toHaveProperty('complexUnknownOption', complexObject);
933+
expect(result).toHaveProperty('arrayOption', ['item1', 'item2']);
934+
expect(result).toHaveProperty('numberOption', 42);
935+
expect(result).toHaveProperty('booleanOption', false);
936+
});
937+
938+
it('should preserve unknown options when merging with user config', () => {
939+
const cliArgs: AgentCLIArguments = {
940+
model: {
941+
provider: 'openai',
942+
},
943+
aioSandbox: 'cli-value',
944+
cliOnlyOption: 'cli-only',
945+
};
946+
947+
const userConfig: AgentAppConfig = {
948+
model: {
949+
id: 'user-model',
950+
},
951+
instructions: 'User instructions',
952+
// User config might also have unknown properties
953+
userSpecificOption: 'user-value',
954+
} as any;
955+
956+
const result = buildAppConfig(cliArgs, userConfig);
957+
958+
// Known options should merge correctly
959+
expect(result.model).toEqual({
960+
provider: 'openai', // From CLI
961+
id: 'user-model', // From user config
962+
});
963+
expect(result.instructions).toBe('User instructions');
964+
965+
// Unknown options from both sources should be preserved
966+
expect(result).toHaveProperty('aioSandbox', 'cli-value'); // CLI overrides
967+
expect(result).toHaveProperty('cliOnlyOption', 'cli-only');
968+
expect(result).toHaveProperty('userSpecificOption', 'user-value');
969+
});
970+
971+
it('should handle unknown options with CLI enhancer', () => {
972+
const cliArgs: AgentCLIArguments = {
973+
model: {
974+
provider: 'openai',
975+
},
976+
aioSandbox: 'test-value',
977+
customOption: 'original-value',
978+
};
979+
980+
const userConfig: AgentAppConfig = {};
981+
982+
const enhancer: any = (cliArguments: any, appConfig: any) => {
983+
// Enhancer might modify unknown options
984+
if (cliArguments.customOption) {
985+
appConfig.enhancedCustomOption = `enhanced-${cliArguments.customOption}`;
986+
}
987+
};
988+
989+
const result = buildAppConfig(cliArgs, userConfig, undefined, enhancer);
990+
991+
// Original unknown options should be preserved
992+
expect(result).toHaveProperty('aioSandbox', 'test-value');
993+
expect(result).toHaveProperty('customOption', 'original-value');
994+
995+
// Enhancer modifications should also be present
996+
expect(result).toHaveProperty('enhancedCustomOption', 'enhanced-original-value');
997+
});
998+
999+
it('should not include known options in unknown options preservation', () => {
1000+
const cliArgs: AgentCLIArguments = {
1001+
// All known options
1002+
model: { provider: 'openai' },
1003+
thinking: { type: 'enabled' },
1004+
toolCallEngine: 'native',
1005+
share: { provider: 'test' },
1006+
snapshot: { enable: true },
1007+
logLevel: 'info' as any,
1008+
server: { exclusive: true },
1009+
port: 3000,
1010+
provider: 'deprecated-provider',
1011+
apiKey: 'deprecated-key', // secretlint-disable-line
1012+
baseURL: 'deprecated-url',
1013+
shareProvider: 'deprecated-share',
1014+
config: ['config.json'],
1015+
debug: true,
1016+
quiet: false,
1017+
stream: true,
1018+
open: true,
1019+
agent: 'test-agent',
1020+
headless: true,
1021+
input: 'test',
1022+
format: 'json',
1023+
includeLogs: true,
1024+
useCache: true,
1025+
workspace: '/workspace',
1026+
1027+
// Unknown option
1028+
unknownOption: 'should-be-preserved',
1029+
};
1030+
1031+
const result = buildAppConfig(cliArgs, {});
1032+
1033+
// All the known options should be properly processed, not duplicated as unknown
1034+
expect(result.model?.provider).toBe('openai');
1035+
expect(result.thinking?.type).toBe('enabled');
1036+
expect(result.toolCallEngine).toBe('native');
1037+
expect(result.share?.provider).toBe('test');
1038+
expect(result.snapshot?.enable).toBe(true);
1039+
expect(result.server?.exclusive).toBe(true);
1040+
expect(result.server?.port).toBe(3000);
1041+
1042+
// Unknown option should be preserved
1043+
expect(result).toHaveProperty('unknownOption', 'should-be-preserved');
1044+
1045+
// Known CLI-only options should not appear
1046+
expect(result).not.toHaveProperty('agent');
1047+
expect(result).not.toHaveProperty('workspace');
1048+
expect(result).not.toHaveProperty('debug');
1049+
expect(result).not.toHaveProperty('config');
1050+
});
1051+
});
8071052
});

0 commit comments

Comments
 (0)