Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions multimodal/tarko/agent-cli/src/config/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function buildAppConfig<
config = deepMerge(config, workspaceConfig);
}

// Extract CLI-specific properties
// Extract known CLI options, everything else (including unknown options) goes to cliConfigProps
const {
agent,
workspace,
Expand All @@ -65,35 +65,37 @@ export function buildAppConfig<
quiet,
port,
stream,
// Extract core deprecated options
headless,
input,
format,
includeLogs,
useCache,
open,
provider,
apiKey,
baseURL,
shareProvider,
thinking,
// Extract tool filter options
tool,
// Extract MCP server filter options
mcpServer,
// Extract server options
server,
...cliConfigProps
} = cliArguments;

// Handle deprecated options
const deprecatedOptions = {
const deprecatedOptionValues = {
provider,
apiKey: apiKey || undefined,
baseURL,
shareProvider,
thinking,
}; // secretlint-disable-line @secretlint/secretlint-rule-pattern
const deprecatedKeys = Object.entries(deprecatedOptions)
const deprecatedKeys = Object.entries(deprecatedOptionValues)
.filter(([, value]) => value !== undefined)
.map(([optionName]) => optionName);

logDeprecatedWarning(deprecatedKeys);
handleCoreDeprecatedOptions(cliConfigProps, deprecatedOptions);
handleCoreDeprecatedOptions(cliConfigProps, deprecatedOptionValues);

// Handle tool filters
handleToolFilterOptions(cliConfigProps, { tool });
Expand Down
7 changes: 7 additions & 0 deletions multimodal/tarko/agent-cli/src/core/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,9 @@ export class AgentCLI {
// Apply agent-specific configurations for commands that run agents
configuredCommand = this.configureAgentCommand(configuredCommand);

// Allow unknown options to be passed through to agents
configuredCommand.allowUnknownOptions();

configuredCommand.action(async (cliArguments: AgentCLIArguments = {}) => {
this.printLogo();

Expand Down Expand Up @@ -203,6 +206,10 @@ export class AgentCLI {

// Apply agent-specific configurations for commands that run agents
configuredCommand = this.configureAgentCommand(configuredCommand);

// Allow unknown options to be passed through to agents
configuredCommand.allowUnknownOptions();

configuredCommand.action(async (...args: any[]) => {
// Handle dynamic arguments due to optional positional parameters [run] [agent]
// CAC passes arguments in this pattern:
Expand Down
245 changes: 245 additions & 0 deletions multimodal/tarko/agent-cli/tests/config-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -804,4 +804,249 @@ describe('buildAppConfig', () => {
expect(result.model?.provider).toBe('openai');
});
});

describe('unknown options passthrough', () => {
it('should preserve unknown CLI options in the final config', () => {
const cliArgs: AgentCLIArguments = {
model: {
provider: 'openai',
id: 'gpt-4',
},
// Unknown options that should be preserved
aioSandbox: 'test-sandbox-value',
customOption: 'custom-value',
nestedUnknown: {
nested: 'value',
},
};

const result = buildAppConfig(cliArgs, {});

// Known options should work as expected
expect(result.model).toEqual({
provider: 'openai',
id: 'gpt-4',
});

// Unknown options should be preserved
expect(result).toHaveProperty('aioSandbox', 'test-sandbox-value');
expect(result).toHaveProperty('customOption', 'custom-value');
expect(result).toHaveProperty('nestedUnknown', {
nested: 'value',
});
});

it('should preserve unknown options while filtering out known CLI-only options', () => {
const cliArgs: AgentCLIArguments = {
// Known CLI-only options that should be filtered out
agent: 'agent-tars',
workspace: '/workspace',
debug: true,
quiet: false,
headless: true,
input: 'test input',
format: 'json',
includeLogs: true,
useCache: false,
open: true,

// Known options that should be preserved
model: {
provider: 'openai',
},

// Unknown options that should be preserved
aioSandbox: 'sandbox-value',
customAgentOption: 'agent-specific-value',
};

const result = buildAppConfig(cliArgs, {});

// Known CLI-only options should not appear in result
expect(result).not.toHaveProperty('agent');
expect(result).not.toHaveProperty('workspace');
expect(result).not.toHaveProperty('debug');
expect(result).not.toHaveProperty('quiet');
expect(result).not.toHaveProperty('headless');
expect(result).not.toHaveProperty('input');
expect(result).not.toHaveProperty('format');
expect(result).not.toHaveProperty('includeLogs');
expect(result).not.toHaveProperty('useCache');
expect(result).not.toHaveProperty('open');

// Known options should be preserved
expect(result.model).toEqual({
provider: 'openai',
});

// Unknown options should be preserved
expect(result).toHaveProperty('aioSandbox', 'sandbox-value');
expect(result).toHaveProperty('customAgentOption', 'agent-specific-value');
});

it('should preserve unknown options alongside deprecated options', () => {
const cliArgs: AgentCLIArguments = {
// Deprecated options
provider: 'openai',
apiKey: 'deprecated-key', // secretlint-disable-line

// Unknown options
aioSandbox: 'test-value',
customFeature: true,
};

const result = buildAppConfig(cliArgs, {});

// Deprecated options should be handled normally
expect(result.model).toEqual({
provider: 'openai',
apiKey: 'deprecated-key', // secretlint-disable-line
});

// Unknown options should be preserved
expect(result).toHaveProperty('aioSandbox', 'test-value');
expect(result).toHaveProperty('customFeature', true);
});

it('should handle unknown options with complex data types', () => {
const complexObject = {
nested: {
array: [1, 2, 3],
boolean: true,
string: 'test',
},
};

const cliArgs: AgentCLIArguments = {
model: {
provider: 'openai',
},
complexUnknownOption: complexObject,
arrayOption: ['item1', 'item2'],
numberOption: 42,
booleanOption: false,
};

const result = buildAppConfig(cliArgs, {});

expect(result).toHaveProperty('complexUnknownOption', complexObject);
expect(result).toHaveProperty('arrayOption', ['item1', 'item2']);
expect(result).toHaveProperty('numberOption', 42);
expect(result).toHaveProperty('booleanOption', false);
});

it('should preserve unknown options when merging with user config', () => {
const cliArgs: AgentCLIArguments = {
model: {
provider: 'openai',
},
aioSandbox: 'cli-value',
cliOnlyOption: 'cli-only',
};

const userConfig: AgentAppConfig = {
model: {
id: 'user-model',
},
instructions: 'User instructions',
// User config might also have unknown properties
userSpecificOption: 'user-value',
} as any;

const result = buildAppConfig(cliArgs, userConfig);

// Known options should merge correctly
expect(result.model).toEqual({
provider: 'openai', // From CLI
id: 'user-model', // From user config
});
expect(result.instructions).toBe('User instructions');

// Unknown options from both sources should be preserved
expect(result).toHaveProperty('aioSandbox', 'cli-value'); // CLI overrides
expect(result).toHaveProperty('cliOnlyOption', 'cli-only');
expect(result).toHaveProperty('userSpecificOption', 'user-value');
});

it('should handle unknown options with CLI enhancer', () => {
const cliArgs: AgentCLIArguments = {
model: {
provider: 'openai',
},
aioSandbox: 'test-value',
customOption: 'original-value',
};

const userConfig: AgentAppConfig = {};

const enhancer: any = (cliArguments: any, appConfig: any) => {
// Enhancer might modify unknown options
if (cliArguments.customOption) {
appConfig.enhancedCustomOption = `enhanced-${cliArguments.customOption}`;
}
};

const result = buildAppConfig(cliArgs, userConfig, undefined, enhancer);

// Original unknown options should be preserved
expect(result).toHaveProperty('aioSandbox', 'test-value');
expect(result).toHaveProperty('customOption', 'original-value');

// Enhancer modifications should also be present
expect(result).toHaveProperty('enhancedCustomOption', 'enhanced-original-value');
});

it('should not include known options in unknown options preservation', () => {
const cliArgs: AgentCLIArguments = {
// All known options
model: { provider: 'openai' },
thinking: { type: 'enabled' },
toolCallEngine: 'native',
share: { provider: 'test' },
snapshot: { enable: true },
logLevel: 'info' as any,
server: { exclusive: true },
port: 3000,
provider: 'deprecated-provider',
apiKey: 'deprecated-key', // secretlint-disable-line
baseURL: 'deprecated-url',
shareProvider: 'deprecated-share',
config: ['config.json'],
debug: true,
quiet: false,
stream: true,
open: true,
agent: 'test-agent',
headless: true,
input: 'test',
format: 'json',
includeLogs: true,
useCache: true,
workspace: '/workspace',

// Unknown option
unknownOption: 'should-be-preserved',
};

const result = buildAppConfig(cliArgs, {});

// All the known options should be properly processed, not duplicated as unknown
expect(result.model?.provider).toBe('openai');
expect(result.thinking?.type).toBe('enabled');
expect(result.toolCallEngine).toBe('native');
expect(result.share?.provider).toBe('test');
expect(result.snapshot?.enable).toBe(true);
expect(result.server?.exclusive).toBe(true);
expect(result.server?.port).toBe(3000);

// Unknown option should be preserved
expect(result).toHaveProperty('unknownOption', 'should-be-preserved');

// Known CLI-only options should not appear
expect(result).not.toHaveProperty('agent');
expect(result).not.toHaveProperty('workspace');
expect(result).not.toHaveProperty('debug');
expect(result).not.toHaveProperty('config');
});
});
});