Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
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
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
AgentAppConfig,
} from '@tarko/interface';
import { AgentSnapshot } from '@tarko/agent-snapshot';
import { filterDeclaredRuntimeSettings, sanitizeSessionAgentOptions } from '@tarko/shared-utils';
import { EventStreamBridge } from '../../utils/event-stream';
import type { AgentServer, ILogger } from '../../types';
import { AgioEvent } from '@tarko/agio';
Expand Down Expand Up @@ -178,22 +179,45 @@ export class AgentSession {

// Apply runtime settings transformation if available
const runtimeSettingsConfig = this.server.appConfig?.server?.runtimeSettings;
let transformedOptions = sessionInfo?.metadata?.runtimeSettings ?? {};

if (runtimeSettingsConfig?.transform && sessionInfo?.metadata?.runtimeSettings) {
// Runtime settings arrive from the client and land in session metadata, so
// reduce them to the keys the server declared before they reach the Agent.
const { value: declaredRuntimeSettings } = filterDeclaredRuntimeSettings(
sessionInfo?.metadata?.runtimeSettings,
runtimeSettingsConfig?.schema,
);
const hasDeclaredRuntimeSettings = Object.keys(declaredRuntimeSettings).length > 0;

// A server-provided transform maps settings onto agent options; its output is
// server code and stays free to override configuration. Without a transform the
// declared settings are passed through as plain values instead.
let trustedOverrides: Record<string, any> = {};
let clientRuntimeOverrides: Record<string, any> = declaredRuntimeSettings;

if (runtimeSettingsConfig?.transform && hasDeclaredRuntimeSettings) {
clientRuntimeOverrides = {};
try {
transformedOptions = runtimeSettingsConfig.transform(sessionInfo.metadata.runtimeSettings);
trustedOverrides = runtimeSettingsConfig.transform(declaredRuntimeSettings) ?? {};
} catch (error) {
console.warn('Failed to apply runtime settings transform:', error);
}
}

// Merge base options with transformed runtime settings and one-time agent options
// One-time options are allowlisted at the API boundary; re-apply the allowlist
// here so neither another caller nor persisted session metadata can widen it.
const { value: requestAgentOptions } = sanitizeSessionAgentOptions(this.agentOptions);
const { value: storedAgentOptions } = sanitizeSessionAgentOptions(
this.sessionInfo?.metadata?.agentOptions,
);

// Client-derived values are applied first so server configuration always wins.
// Only the server's own transform output may override it.
const agentOptions = {
...clientRuntimeOverrides,
...(requestAgentOptions ?? {}),
...(storedAgentOptions ?? {}),
...baseAgentOptions,
...transformedOptions,
...(this.agentOptions || {}), // Apply one-time agent initialization options
...(this.sessionInfo?.metadata?.agentOptions || {}),
...trustedOverrides,
};

// Create base agent
Expand Down Expand Up @@ -254,7 +278,7 @@ export class AgentSession {
id: agentOptions.model?.id,
provider: agentOptions.model?.provider,
},
runtimeSettings: transformedOptions,
runtimeSettings: declaredRuntimeSettings,
},
null,
2,
Expand Down
Loading
Loading