identark
v1.0.3
Published
The AgentGateway Protocol — secure, scalable agent execution infrastructure
Downloads
656
Maintainers
Readme
identark
The AgentGateway Protocol — secure, scalable agent execution infrastructure.
A TypeScript SDK for building agents that hold zero secrets and maintain zero state. Route requests through your own LLM provider (local development) or the IdentArk control plane (production). Perfect for multi-turn conversations, function calling, file I/O, and cost tracking.
Features
- Agent Protocol:
AgentGatewayinterface enables seamless swapping between local and cloud execution - DirectGateway: Call OpenAI, Anthropic, Mistral, or any OpenAI-compatible endpoint (Ollama, vLLM) directly
- ControlPlaneGateway: Production-grade routing through IdentArk control plane with automatic env var detection
- Zero Secrets: Agents never hold API keys or credentials
- Cost Tracking: Built-in pricing tables and session cost calculation
- Streaming Support: Token-by-token streaming from any provider
- Tool Calling: Full support for OpenAI-style function definitions and tool calls
- File I/O: Presigned URLs for secure workspace file access
- Testing:
MockGatewayfor unit testing agent logic - Zero Dependencies: Native
fetchAPI, no required runtime deps - TypeScript First: Full type safety, strict mode enabled
Installation
npm install identark
# Optional peer dependencies for type hints
npm install --save-peer openai @anthropic-ai/sdkQuick Start
Local Development with OpenAI
import { DirectGateway, Message, Role } from "identark";
import { OpenAI } from "openai";
const gateway = new DirectGateway(
new OpenAI(),
"gpt-4o",
"You are a helpful assistant."
);
const response = await gateway.invokeLlm([
{ role: Role.USER, content: "What is 2 + 2?" }
]);
console.log(response.message.content);
console.log(`Cost: $${response.cost_usd.toFixed(4)}`);Local Development with Anthropic
import { DirectGateway, Role } from "identark";
import { Anthropic } from "@anthropic-ai/sdk";
const gateway = new DirectGateway(
new Anthropic(),
"claude-3-5-sonnet-20241022"
);
const response = await gateway.invokeLlm([
{ role: Role.USER, content: "Hello Claude!" }
]);Production with IdentArk Control Plane
import { ControlPlaneGateway, Role } from "identark";
// Auto-detects from env vars:
// - IDENTARK_SESSION_TOKEN (inside sandbox)
// - IDENTARK_API_KEY (outside sandbox)
// - IDENTARK_CONTROL_PLANE_URL
// - IDENTARK_SESSION_ID (optional)
const gateway = new ControlPlaneGateway();
const response = await gateway.invokeLlm([
{ role: Role.USER, content: "Hello from production!" }
]);Streaming Responses
const gateway = new DirectGateway(new OpenAI(), "gpt-4o");
for await (const chunk of gateway.invokeLlmStream([
{ role: Role.USER, content: "Write a haiku about TypeScript." }
])) {
if (chunk.content) {
process.stdout.write(chunk.content);
}
}Function Calling
const tools = [
{
type: "function",
function: {
name: "get_weather",
description: "Get the weather for a location",
parameters: {
type: "object",
properties: {
location: { type: "string" }
},
required: ["location"]
}
}
}
];
const response = await gateway.invokeLlm(
[{ role: Role.USER, content: "What's the weather in NYC?" }],
tools
);
if (response.tool_calls) {
for (const call of response.tool_calls) {
const args = JSON.parse(call.function.arguments);
console.log(`Calling ${call.function.name} with`, args);
}
}File Operations
// Request a presigned URL for workspace file access
const presigned = await gateway.requestFileUrl("/workspace/data.json", "PUT");
// Use the URL to upload/download files (valid for ~24 hours)
const response = await fetch(presigned.url, {
method: "PUT",
body: JSON.stringify({ data: "value" })
});Cost Tracking
const gateway = new DirectGateway(
new OpenAI(),
"gpt-4o",
undefined,
0.10 // $0.10 cost cap
);
try {
await gateway.invokeLlm([{ role: Role.USER, content: "..." }]);
} catch (err) {
if (err instanceof CostCapExceededError) {
console.log(`Cost cap exceeded: $${err.consumed_usd}/$${err.cap_usd}`);
}
}
const totalCost = await gateway.getSessionCost();
console.log(`Session total: $${totalCost.toFixed(4)}`);Testing with MockGateway
import { MockGateway, Role, LLMResponse } from "identark";
const mockResponse: LLMResponse = {
message: { role: Role.ASSISTANT, content: "Mocked response" },
cost_usd: 0.001,
model: "mock",
finish_reason: "stop",
usage: { input_tokens: 10, output_tokens: 5, total_tokens: 15 }
};
const gateway = new MockGateway([mockResponse]);
const response = await gateway.invokeLlm([
{ role: Role.USER, content: "Test message" }
]);
// Assert on calls
console.log(gateway.invokeLlmCallCount); // 1
console.log(gateway.totalMessagesSent); // 1API Reference
Types
Role enum
Message roles: USER, ASSISTANT, TOOL, SYSTEM
Message interface
interface Message {
role: Role;
content: string | Record<string, unknown>[];
tool_call_id?: string;
name?: string;
tokens?: number;
}LLMResponse interface
interface LLMResponse {
message: Message;
cost_usd: number;
model: string;
finish_reason: string;
tool_calls?: ToolCall[];
usage?: TokenUsage;
}StreamChunk interface
interface StreamChunk {
content: string;
finish_reason: string | null;
model: string;
input_tokens?: number;
output_tokens?: number;
}PresignedURL interface
interface PresignedURL {
url: string;
expires_at: string;
method: string;
file_path: string;
}Gateways
AgentGateway interface
The core protocol. Implement this to create custom gateways:
interface AgentGateway {
invokeLlm(
newMessages: Message[],
tools?: Record<string, unknown>[],
toolChoice?: string | Record<string, unknown>
): Promise<LLMResponse>;
persistMessages(messages: Message[]): Promise<void>;
requestFileUrl(filePath: string, method?: string): Promise<PresignedURL>;
getSessionCost(): Promise<number>;
invokeLlmStream(
newMessages: Message[],
tools?: Record<string, unknown>[],
toolChoice?: string | Record<string, unknown>
): AsyncGenerator<StreamChunk>;
}DirectGateway
Local development gateway. Calls LLM providers directly.
new DirectGateway(
llmClient: unknown, // OpenAI, Anthropic, or compatible client
model: string, // Model ID
systemPrompt?: string, // Optional system prompt
costCapUsd?: number, // Optional cost cap
workspaceDir?: string, // Local workspace directory (default: /workspace)
provider?: string // Explicit provider: 'openai' | 'anthropic' | 'mistral' | 'local'
)ControlPlaneGateway
Production gateway. Routes through IdentArk control plane.
new ControlPlaneGateway(
apiKey?: string, // Auto-detected from env
url?: string, // Auto-detected from env
sessionId?: string, // Optional session ID
timeout?: number, // Request timeout in seconds (default: 30)
maxRetries?: number // Retry attempts (default: 3)
)MockGateway
Test double for unit testing.
const mock = new MockGateway(
responses?: LLMResponse[], // Responses to return in order
defaultResponse?: LLMResponse, // Fallback response
workspaceDir?: string // Workspace for file URLs
);
// Record calls for assertions
mock.invokeLlmCallCount
mock.totalMessagesSent
mock.lastRequest
mock.allInvokeCalls
mock.allPersistedMessagesError Handling
All errors inherit from IdentArkError:
import {
IdentArkError,
CostCapExceededError,
AuthenticationError,
RateLimitError,
ContentPolicyError,
PathNotAllowedError,
ConfigurationError
} from "identark";
try {
const response = await gateway.invokeLlm([msg]);
} catch (err) {
if (err instanceof CostCapExceededError) {
console.log(`Over budget: $${err.consumed_usd}/$${err.cap_usd}`);
} else if (err instanceof AuthenticationError) {
console.log(`Auth failed: ${err.reason}`);
} else if (err instanceof RateLimitError) {
console.log(`Rate limited. Retry after ${err.retry_after_seconds}s`);
} else if (err instanceof IdentArkError) {
console.error(`SDK error: ${err.message}`);
} else {
throw err;
}
}Provider Support
DirectGateway supports:
| Provider | Client | Example |
|----------|--------|---------|
| OpenAI | openai npm package | gpt-4o, gpt-4-turbo |
| Anthropic | @anthropic-ai/sdk | claude-3-5-sonnet-20241022 |
| Mistral | OpenAI SDK (base_url) | mistral-large-latest |
| Ollama | OpenAI SDK (local) | llama3.2, custom models |
| vLLM | OpenAI SDK (base_url) | Any HuggingFace model |
Cost Estimation
Built-in pricing tables for popular models. Unknown models use a conservative default. Local providers (Ollama) always cost $0.00.
// Pricing is automatic
const response = await gateway.invokeLlm([msg]);
console.log(`This call cost: $${response.cost_usd.toFixed(6)}`);Environment Variables
For ControlPlaneGateway:
IDENTARK_SESSION_TOKEN # Inside IdentArk sandbox (auto-created)
IDENTARK_API_KEY # Outside sandbox (your API key)
IDENTARK_CONTROL_PLANE_URL # Control plane endpoint
IDENTARK_SESSION_ID # Optional: explicit session IDDevelopment
# Install dependencies
npm install
# Type check
npm run typecheck
# Run tests
npm test
# Build
npm run build
# Watch mode
npm run devMigration from Python SDK
The TypeScript SDK mirrors the Python SDK's design. Here's the mapping:
| Python | TypeScript |
|--------|-----------|
| Role enum | Role enum |
| Message dataclass | Message interface |
| LLMResponse dataclass | LLMResponse interface |
| AgentGateway Protocol | AgentGateway interface |
| DirectGateway class | DirectGateway class |
| ControlPlaneGateway class | ControlPlaneGateway class |
| MockGateway class | MockGateway class |
Main differences:
- Use constructor parameters instead of
@dataclassfields - Use async/await instead of
async def - Use
AsyncGeneratorinstead of Python's async generator syntax - Use fetch API instead of httpx
- Property getters (
getModel()) instead of@propertydecorators
License
MIT
See LICENSE file for details.
Contributing
Contributions welcome! Please ensure tests pass and TypeScript is strictly typed.
npm test
npm run typecheck
npm run lint