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
5 changes: 5 additions & 0 deletions .changeset/tenki-sandbox-provider.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@inkeep/agents-api': patch
---

Add Tenki as a sandbox provider for function tool execution. Configure it with `sandboxConfig: { provider: 'tenki', ... }` or by setting `SANDBOX_TENKI_API_KEY` in the environment; function tools then run in isolated Tenki Sandbox microVMs with pooled sessions, matching the existing Vercel provider behavior.
3 changes: 2 additions & 1 deletion agents-api/__snapshots__/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -2023,7 +2023,8 @@
"description": "The configured sandbox provider, if enabled.",
"enum": [
"native",
"vercel"
"vercel",
"tenki"
],
"type": "string"
},
Expand Down
1 change: 1 addition & 0 deletions agents-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
"@opentelemetry/sdk-trace-base": "catalog:",
"@opentelemetry/semantic-conventions": "catalog:",
"@smithy/node-http-handler": "^4.4.0",
"@tenkicloud/sandbox": "^0.3.6",
"@vercel/blob": "^2.3.0",
"@vercel/functions": "^3.5.1",
"@vercel/queue": "^0.1.6",
Expand Down
72 changes: 71 additions & 1 deletion agents-api/src/domains/run/tools/SandboxExecutorFactory.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import crypto from 'node:crypto';
import { getLogger } from '../../../logger';
import type { SandboxConfig, VercelSandboxConfig } from '../types/executionContext';
import type {
SandboxConfig,
TenkiSandboxConfig,
VercelSandboxConfig,
} from '../types/executionContext';
import { type FunctionToolConfig, NativeSandboxExecutor } from './NativeSandboxExecutor';
import { TenkiSandboxExecutor } from './TenkiSandboxExecutor';
import { VercelSandboxExecutor } from './VercelSandboxExecutor';

const logger = getLogger('SandboxExecutorFactory');
Expand All @@ -14,6 +20,7 @@ export class SandboxExecutorFactory {
private static sessionFactories: Map<string, SandboxExecutorFactory> = new Map();
private nativeExecutor: NativeSandboxExecutor | null = null;
private vercelExecutors: Map<string, VercelSandboxExecutor> = new Map();
private tenkiExecutors: Map<string, TenkiSandboxExecutor> = new Map();

public constructor() {
logger.info('SandboxExecutorFactory initialized');
Expand Down Expand Up @@ -73,6 +80,10 @@ export class SandboxExecutorFactory {
return this.executeInVercelSandbox(functionId, args, config);
}

if (sandboxConfig.provider === 'tenki') {
return this.executeInTenkiSandbox(functionId, args, config);
}

throw new Error(`Unknown sandbox provider: ${(sandboxConfig as SandboxConfig).provider}`);
}

Expand Down Expand Up @@ -132,6 +143,60 @@ export class SandboxExecutorFactory {
return result.result;
}

/**
* Execute in Tenki sandbox
*/
private async executeInTenkiSandbox(
functionId: string,
args: Record<string, unknown>,
config: FunctionToolConfig
): Promise<unknown> {
const tenkiConfig = config.sandboxConfig as TenkiSandboxConfig;

// Key executors by the full configuration so distinct credentials,
// projects, or resource settings never share a client (token is hashed,
// never stored raw)
const configKey = crypto
.createHash('sha256')
.update(
JSON.stringify([
tenkiConfig.authToken,
tenkiConfig.baseUrl,
tenkiConfig.projectId,
tenkiConfig.runtime,
tenkiConfig.timeout,
tenkiConfig.vcpus,
])
)
.digest('hex')
.substring(0, 16);

// Get or create Tenki executor for this configuration
if (!this.tenkiExecutors.has(configKey)) {
const executor = new TenkiSandboxExecutor(tenkiConfig);
this.tenkiExecutors.set(configKey, executor);
logger.info(
{
baseUrl: tenkiConfig.baseUrl,
},
'Tenki sandbox executor created'
);
}

const executor = this.tenkiExecutors.get(configKey);
if (!executor) {
throw new Error(`Failed to get Tenki executor for config: ${configKey}`);
}

const result = await executor.executeFunctionTool(functionId, args, config);

if (!result.success) {
throw new Error(result.error || 'Tenki sandbox execution failed');
}

return result.result;
}

/**
* Clean up all sandbox executors
*/
Expand All @@ -148,6 +213,11 @@ export class SandboxExecutorFactory {
this.vercelExecutors.delete(key);
}

for (const [key, executor] of this.tenkiExecutors.entries()) {
await executor.cleanup();
this.tenkiExecutors.delete(key);
}

logger.info('Sandbox executor cleanup completed');
}
}
Loading