Skip to content
Open
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
16 changes: 13 additions & 3 deletions multimodal/tarko/agent-cli/src/config/builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { deepMerge, isTest } from '@tarko/shared-utils';
import { deepMerge, isTest, resolveServerHost } from '@tarko/shared-utils';
import { getStaticPath } from '@tarko/agent-ui-builder';
import {
CommonFilterOptions,
Expand Down Expand Up @@ -64,6 +64,7 @@ export function buildAppConfig<
debug,
quiet,
port,
host,
stream,
headless,
input,
Expand Down Expand Up @@ -119,7 +120,7 @@ export function buildAppConfig<

// Apply CLI shortcuts
applyLoggingShortcuts(config, { debug, quiet });
applyServerConfiguration(config, { port });
applyServerConfiguration(config, { port, host });

// Apply WebUI defaults
applyWebUIDefaults(config as AgentAppConfig);
Expand Down Expand Up @@ -243,7 +244,10 @@ function parseLogLevel(level: string): LogLevel | undefined {
/**
* Apply server configuration with defaults
*/
function applyServerConfiguration(config: AgentAppConfig, serverOptions: { port?: number }): void {
function applyServerConfiguration(
config: AgentAppConfig,
serverOptions: { port?: number; host?: string },
): void {
if (!config.server) {
config.server = {
port: 8888,
Expand All @@ -259,6 +263,12 @@ function applyServerConfiguration(config: AgentAppConfig, serverOptions: { port?
if (serverOptions.port) {
config.server.port = serverOptions.port;
}

if (serverOptions.host) {
config.server.host = serverOptions.host;
}

config.server.host = resolveServerHost(config.server.host);
}

/**
Expand Down
4 changes: 4 additions & 0 deletions multimodal/tarko/agent-cli/src/core/commands/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { LogLevel } from '@tarko/interface';
import { AgentServer, resolveAgentImplementation } from '@tarko/agent-server';
import { DEFAULT_SERVER_HOST } from '@tarko/shared-utils';
import { ConsoleInterceptor } from '../../utils';
import { AgentCLIRunCommandOptions } from '../../types';

Expand Down Expand Up @@ -73,9 +74,12 @@ export async function processServerRun(options: AgentCLIRunCommandOptions): Prom

const { appConfig } = agentServerInitOptions;

// This server only exists to serve the one-shot request issued below, so keep it
// on loopback regardless of any configured host.
appConfig.server = {
...(appConfig.server || {}),
port: 8899,
host: DEFAULT_SERVER_HOST,
};

const { result, logs } = await ConsoleInterceptor.run(
Expand Down
9 changes: 8 additions & 1 deletion multimodal/tarko/agent-cli/src/core/commands/serve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { LogLevel } from '@tarko/interface';
import { AgentCLIServeCommandOptions } from '../../types';
import { AgentServer } from '@tarko/agent-server';
import { ensureServerConfig } from '../../utils';
import { formatServerUrl, isExternallyReachableHost } from '@tarko/shared-utils';
import boxen from 'boxen';
import chalk from 'chalk';

Expand All @@ -27,14 +28,20 @@ export async function startHeadlessServer(
const httpServer = await server.start();

const port = appConfig.server!.port!;
const serverUrl = `http://localhost:${port}`;
const serverUrl = formatServerUrl(server.host, port);

if (appConfig.logLevel !== LogLevel.SILENT) {
const boxContent = [
`${chalk.bold(`${server.getCurrentAgentName()} Headless Server`)}`,
'',
`${chalk.cyan('API URL:')} ${chalk.underline(serverUrl)}`,
'',
`${chalk.cyan('Bound to:')} ${chalk.yellow(`${server.host}:${port}`)}${
isExternallyReachableHost(server.host)
? ` ${chalk.red('- reachable from the network, and unauthenticated')}`
: ''
}`,
'',
`${chalk.cyan('Mode:')} ${chalk.yellow('Headless (API only)')}`,
].join('\n');

Expand Down
10 changes: 8 additions & 2 deletions multimodal/tarko/agent-cli/src/core/commands/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import boxen from 'boxen';
import chalk from 'chalk';
import gradient from 'gradient-string';
import { logger, toUserFriendlyPath, ensureServerConfig } from '../../utils';
import { createPathMatcher } from '@tarko/shared-utils';
import { createPathMatcher, formatServerUrl, isExternallyReachableHost } from '@tarko/shared-utils';
import { AgentCLIRunInteractiveUICommandOptions } from '../../types';

/**
Expand Down Expand Up @@ -56,7 +56,7 @@ export async function startInteractiveWebUI(
}

const port = appConfig.server!.port!;
const serverUrl = `http://localhost:${port}`;
const serverUrl = formatServerUrl(server.host, port);

if (appConfig.logLevel !== LogLevel.SILENT) {
// Define brand colors
Expand All @@ -76,6 +76,12 @@ export async function startInteractiveWebUI(
'',
`📁 ${chalk.gray('Workspace:')} ${brandGradient(workspaceDir)}`,
'',
`🔌 ${chalk.gray('Bound to:')} ${brandGradient(`${server.host}:${port}`)}${
isExternallyReachableHost(server.host)
? ` ${chalk.red('- reachable from the network, and unauthenticated')}`
: ''
}`,
'',
`🤖 ${chalk.gray('Model:')} ${appConfig.model?.provider ? brandGradient(`${provider} | ${modelId}`) : chalk.gray('Not specified')}`,
].join('\n');

Expand Down
10 changes: 10 additions & 0 deletions multimodal/tarko/agent-cli/src/core/options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import { Command } from 'cac';
import { AgentCLIArguments, AgentImplementation } from '@tarko/interface';
import { DEFAULT_SERVER_HOST } from '@tarko/shared-utils';
import { AgioProvider } from '../agio/AgioProvider';

export type { AgentCLIArguments };
Expand All @@ -17,6 +18,15 @@ export const DEFAULT_PORT = 8888;
export function addCommonOptions(command: Command): Command {
const baseCommand = command
.option('--port <port>', 'Port to run the server on', { default: DEFAULT_PORT })
.option(
'--host <host>',
`Network interface to bind (default: ${DEFAULT_SERVER_HOST})

The server exposes agent execution without authentication, so it binds
loopback only by default. Pass --host 0.0.0.0 to listen on every
interface, and only do so behind a proxy that authenticates requests.
`,
)
.option('--open', 'Open the web UI in the default browser on server start')
.option(
'--config, -c <path>',
Expand Down
10 changes: 3 additions & 7 deletions multimodal/tarko/agent-cli/src/utils/server-setup.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
/*
* Copyright (c) 2025 Bytedance, Inc. and its affiliates.
* SPDX-License-Identifier: Apache-2.0
*/

import { AgentAppConfig } from '@tarko/interface';
import { resolveServerHost } from '@tarko/shared-utils';
import chalk from 'chalk';
import { findAvailablePort } from './port';

export async function ensureServerConfig(appConfig: AgentAppConfig): Promise<void> {
// Ensure server config exists with defaults
if (!appConfig.server) {
appConfig.server = {
port: 8888,
};
}

// Find available port
appConfig.server.host = resolveServerHost(appConfig.server.host);

const availablePort = await findAvailablePort(appConfig.server.port!);
if (availablePort !== appConfig.server.port) {
console.log(
Expand Down
8 changes: 8 additions & 0 deletions multimodal/tarko/agent-cli/tests/config-builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ describe('buildAppConfig', () => {
"provider": "openai",
},
"server": {
"host": "127.0.0.1",
"port": 3000,
"storage": {
"type": "sqlite",
Expand Down Expand Up @@ -234,6 +235,7 @@ describe('buildAppConfig', () => {
const result = buildAppConfig(cliArgs, userConfig);

expect(result.server).toEqual({
host: '127.0.0.1',
port: 8888, // Default port
storage: {
type: 'sqlite',
Expand All @@ -255,6 +257,7 @@ describe('buildAppConfig', () => {
const result = buildAppConfig(cliArgs, userConfig);

expect(result.server).toEqual({
host: '127.0.0.1',
port: 3000, // CLI overrides user config
storage: {
type: 'sqlite',
Expand Down Expand Up @@ -300,6 +303,7 @@ describe('buildAppConfig', () => {
const result = buildAppConfig(cliArgs, {});

expect(result.server).toEqual({
host: '127.0.0.1',
port: 8888, // Default port always added
storage: {
type: 'sqlite',
Expand Down Expand Up @@ -340,6 +344,7 @@ describe('buildAppConfig', () => {
"provider": "openai",
},
"server": {
"host": "127.0.0.1",
"port": 8888,
"storage": {
"type": "sqlite",
Expand Down Expand Up @@ -455,6 +460,7 @@ describe('buildAppConfig', () => {
"provider": "openai",
},
"server": {
"host": "127.0.0.1",
"port": 8888,
"storage": {
"type": "sqlite",
Expand Down Expand Up @@ -612,6 +618,7 @@ describe('buildAppConfig', () => {
const result = buildAppConfig(cliArgs, userConfig);

expect(result.server).toEqual({
host: '127.0.0.1',
port: 9999,
storage: {
type: 'file',
Expand Down Expand Up @@ -681,6 +688,7 @@ describe('buildAppConfig', () => {
const result = buildAppConfig(cliArgs, userConfig);

expect(result.server).toEqual({
host: '127.0.0.1',
port: 9999,
storage: {
type: 'file',
Expand Down
40 changes: 39 additions & 1 deletion multimodal/tarko/agent-server-next/src/controllers/sessions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import type { HonoContext } from '../types';
import { getCurrentUserId } from '../middlewares/auth';
import { SessionInfo } from '@tarko/interface';
import { ShareService } from '../services';
import { InvalidSessionInputError } from '../services/session/AgentSessionFactory';
import {
ALLOWED_SESSION_AGENT_OPTION_KEYS,
filterDeclaredRuntimeSettings,
sanitizeSessionAgentOptions,
} from '@tarko/shared-utils';
import { filterSessionModel } from '../utils';

/**
Expand Down Expand Up @@ -67,6 +73,9 @@ export async function createSession(c: HonoContext) {
201,
);
} catch (error) {
if (error instanceof InvalidSessionInputError) {
return c.json({ error: error.message, ...error.details }, 400);
}
console.error('Failed to create session:', error);
return c.json({ error: 'Failed to create session' }, 500);
}
Expand Down Expand Up @@ -200,10 +209,39 @@ export async function updateSession(c: HonoContext) {
return c.json({ error: 'Session not found' }, 404);
}

// Session metadata is replayed into the Agent constructor on every session
// initialization, so hold its agent-facing fields to the same boundary as
// session creation instead of persisting the payload verbatim.
const sanitizedUpdates = { ...metadataUpdates };

if ('agentOptions' in sanitizedUpdates) {
const { value, rejectedKeys } = sanitizeSessionAgentOptions(sanitizedUpdates.agentOptions);
if (rejectedKeys.length > 0) {
return c.json(
{
error: 'Unsupported agentOptions',
message: `agentOptions may only contain ${ALLOWED_SESSION_AGENT_OPTION_KEYS.join(', ')}; rejected: ${rejectedKeys.join(', ')}. Configure anything else on the server.`,
allowed: ALLOWED_SESSION_AGENT_OPTION_KEYS,
rejected: rejectedKeys,
},
400,
);
}
sanitizedUpdates.agentOptions = value;
}

if ('runtimeSettings' in sanitizedUpdates) {
const { value } = filterDeclaredRuntimeSettings(
sanitizedUpdates.runtimeSettings,
server.appConfig?.server?.runtimeSettings?.schema,
);
sanitizedUpdates.runtimeSettings = value;
}

const updatedMetadata = await server.daoFactory.updateSessionInfo(sessionId, {
metadata: {
...sessionInfo.metadata,
...metadataUpdates,
...sanitizedUpdates,
},
});

Expand Down
6 changes: 5 additions & 1 deletion multimodal/tarko/agent-server-next/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import { SandboxScheduler } from './services/sandbox';
import { UserConfigService } from './services/user';
import { MongoDAOFactory } from './dao/mongodb/MongoDAOFactory';
import { TARKO_CONSTANTS, GlobalDirectoryOptions } from '@tarko/interface';
import { formatServerUrl, resolveServerHost } from '@tarko/shared-utils';
import {
createQueryRoutes,
createSessionRoutes,
Expand Down Expand Up @@ -62,6 +63,7 @@ export class AgentServer<T extends AgentAppConfig = AgentAppConfig> {

// Configuration
public readonly port: number;
public readonly host: string;
public readonly isDebug: boolean;
public readonly isExclusive: boolean;
public readonly daoFactory: IDAOFactory;
Expand Down Expand Up @@ -95,6 +97,7 @@ export class AgentServer<T extends AgentAppConfig = AgentAppConfig> {

// Extract server configuration from agent options
this.port = appConfig.server?.port ?? 3000;
this.host = resolveServerHost(appConfig.server?.host);
this.isDebug = appConfig.logLevel === LogLevel.DEBUG;
this.isExclusive = appConfig.server?.exclusive ?? false;
this.tenantConfig = appConfig.server?.tenant || { mode: 'single', auth: false };
Expand Down Expand Up @@ -349,10 +352,11 @@ export class AgentServer<T extends AgentAppConfig = AgentAppConfig> {
this.server = serve({
fetch: this.app.fetch,
port: this.port,
hostname: this.host,
});

this.isRunning = true;
console.log(`Server started on port ${this.port}`);
console.log(`Server started on ${formatServerUrl(this.host, this.port)} (bound to ${this.host})`);
}

/**
Expand Down
Loading