agent-claw: automated task changes
This commit is contained in:
141
agentclaw/AGENTS.md
Normal file
141
agentclaw/AGENTS.md
Normal file
@@ -0,0 +1,141 @@
|
||||
# AgentCore Project
|
||||
|
||||
This project contains configuration and infrastructure for an Amazon Bedrock AgentCore application.
|
||||
|
||||
The `agentcore/` directory is a declarative model of the project. The `agentcore/cdk/` subdirectory uses the
|
||||
`@aws/agentcore-cdk` L3 constructs to deploy the configuration to AWS.
|
||||
|
||||
## Mental Model
|
||||
|
||||
The project uses a **flat resource model**. Agents, memories, credentials, gateways, evaluators, and policies are
|
||||
independent top-level arrays in `agentcore.json`. There is no binding between resources in the schema — each resource is
|
||||
provisioned independently. Agents discover memories and credentials at runtime via environment variables or SDK calls.
|
||||
Tags defined in `agentcore.json` flow through to deployed CloudFormation resources.
|
||||
|
||||
## Critical Invariants
|
||||
|
||||
1. **Schema-First Authority:** The `.json` files are the source of truth. Do not modify agent behavior by editing
|
||||
generated CDK code in `cdk/`.
|
||||
2. **Resource Identity:** The `name` field determines the CloudFormation Logical ID.
|
||||
- **Renaming** a resource will **destroy and recreate** it.
|
||||
- **Modifying** other fields will update the resource **in-place**.
|
||||
3. **Schema Validation:** If your JSON conforms to the types in `.llm-context/`, it will deploy successfully. Run
|
||||
`agentcore validate` to check.
|
||||
4. **Resource Removal:** Use `agentcore remove` to remove resources. Run `agentcore deploy` after removal to tear down
|
||||
deployed infrastructure.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```
|
||||
myProject/
|
||||
├── AGENTS.md # This file — AI coding assistant context
|
||||
├── agentcore/
|
||||
│ ├── agentcore.json # Main project config (AgentCoreProjectSpec)
|
||||
│ ├── aws-targets.json # Deployment targets (account + region)
|
||||
│ ├── .env.local # Secrets — API keys (gitignored)
|
||||
│ ├── .llm-context/ # TypeScript type definitions for AI assistants
|
||||
│ │ ├── README.md # Guide to using schema files
|
||||
│ │ ├── agentcore.ts # AgentCoreProjectSpec types
|
||||
│ │ ├── aws-targets.ts # AWS deployment target types
|
||||
│ │ └── mcp.ts # Gateway and MCP tool types
|
||||
│ └── cdk/ # AWS CDK project (@aws/agentcore-cdk L3 constructs)
|
||||
├── app/ # Agent application code
|
||||
└── evaluators/ # Custom evaluator code (if any)
|
||||
```
|
||||
|
||||
## Schema Reference
|
||||
|
||||
The `agentcore/.llm-context/` directory contains TypeScript type definitions optimized for AI coding assistants. Each
|
||||
file maps to a JSON config file and includes validation constraints as comments (`@regex`, `@min`, `@max`).
|
||||
|
||||
| JSON Config | Schema File | Root Type |
|
||||
| --- | --- | --- |
|
||||
| `agentcore/agentcore.json` | `agentcore/.llm-context/agentcore.ts` | `AgentCoreProjectSpec` |
|
||||
| `agentcore/agentcore.json` (gateways) | `agentcore/.llm-context/mcp.ts` | `AgentCoreMcpSpec` |
|
||||
| `agentcore/aws-targets.json` | `agentcore/.llm-context/aws-targets.ts` | `AwsDeploymentTarget[]` |
|
||||
|
||||
### Key Types
|
||||
|
||||
- **AgentCoreProjectSpec**: Root config with `runtimes`, `memories`, `credentials`, `agentCoreGateways`, `evaluators`, `onlineEvalConfigs`, `policyEngines` arrays
|
||||
- **AgentEnvSpec**: Agent configuration (build type, entrypoint, code location, runtime version, network mode)
|
||||
- **Memory**: Memory resource with strategies (SEMANTIC, SUMMARIZATION, USER_PREFERENCE, EPISODIC) and expiry
|
||||
- **Credential**: API key or OAuth credential provider
|
||||
- **AgentCoreGateway**: MCP gateway with targets (Lambda, MCP server, OpenAPI, Smithy, API Gateway)
|
||||
- **Evaluator**: LLM-as-a-Judge or code-based evaluator
|
||||
- **OnlineEvalConfig**: Continuous evaluation pipeline bound to an agent
|
||||
|
||||
### Common Enum Values
|
||||
|
||||
- **BuildType**: `'CodeZip'` | `'Container'`
|
||||
- **NetworkMode**: `'PUBLIC'` | `'VPC'`
|
||||
- **RuntimeVersion**: `'PYTHON_3_10'` | `'PYTHON_3_11'` | `'PYTHON_3_12'` | `'PYTHON_3_13'` | `'PYTHON_3_14'` | `'NODE_18'` | `'NODE_20'` | `'NODE_22'`
|
||||
- **MemoryStrategyType**: `'SEMANTIC'` | `'SUMMARIZATION'` | `'USER_PREFERENCE'` | `'EPISODIC'`
|
||||
- **GatewayTargetType**: `'lambda'` | `'mcpServer'` | `'openApiSchema'` | `'smithyModel'` | `'apiGateway'` | `'lambdaFunctionArn'`
|
||||
- **ModelProvider**: `'Bedrock'` | `'Gemini'` | `'OpenAI'` | `'Anthropic'`
|
||||
|
||||
### Build Types
|
||||
|
||||
- **CodeZip**: Python source packaged as a zip and deployed directly to AgentCore Runtime.
|
||||
- **Container**: Docker image built in CodeBuild (ARM64), pushed to a per-agent ECR repository. Requires a `Dockerfile`
|
||||
in the agent's `codeLocation` directory. For local development (`agentcore dev`), the container is built and run
|
||||
locally with volume-mounted hot-reload.
|
||||
|
||||
### Supported Frameworks (for template agents)
|
||||
|
||||
- **Strands** — Bedrock, Anthropic, OpenAI, Gemini
|
||||
- **LangChain/LangGraph** — Bedrock, Anthropic, OpenAI, Gemini
|
||||
- **GoogleADK** — Gemini
|
||||
- **OpenAI Agents** — OpenAI
|
||||
- **Autogen** — Bedrock, Anthropic, OpenAI, Gemini
|
||||
|
||||
### Protocols
|
||||
|
||||
- **HTTP** — Standard HTTP agent endpoint
|
||||
- **MCP** — Model Context Protocol server
|
||||
- **A2A** — Agent-to-Agent protocol (Google A2A)
|
||||
|
||||
## Deployment
|
||||
|
||||
Deployments are orchestrated through the CLI:
|
||||
|
||||
```bash
|
||||
agentcore deploy # Synthesizes CDK and deploys to AWS
|
||||
agentcore status # Shows deployment status
|
||||
```
|
||||
|
||||
Alternatively, deploy directly via CDK:
|
||||
|
||||
```bash
|
||||
cd agentcore/cdk
|
||||
npm install
|
||||
npx cdk synth
|
||||
npx cdk deploy
|
||||
```
|
||||
|
||||
## Editing Schemas
|
||||
|
||||
When modifying JSON config files:
|
||||
|
||||
1. Read the corresponding `agentcore/.llm-context/*.ts` file for type definitions
|
||||
2. Check validation constraint comments (`@regex`, `@min`, `@max`)
|
||||
3. Use exact enum values as string literals
|
||||
4. Use CloudFormation-safe names (alphanumeric, start with letter)
|
||||
5. Run `agentcore validate` to verify changes
|
||||
|
||||
## CLI Commands
|
||||
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `agentcore create` | Create a new project |
|
||||
| `agentcore add <resource>` | Add agent, memory, credential, gateway, evaluator, policy |
|
||||
| `agentcore remove <resource>` | Remove a resource |
|
||||
| `agentcore dev` | Run agent locally with hot-reload |
|
||||
| `agentcore deploy` | Deploy to AWS |
|
||||
| `agentcore status` | Show deployment status |
|
||||
| `agentcore invoke` | Invoke agent (local or deployed) |
|
||||
| `agentcore logs` | View agent logs |
|
||||
| `agentcore traces` | View agent traces |
|
||||
| `agentcore eval` | Run evaluations against an agent |
|
||||
| `agentcore package` | Package agent artifacts |
|
||||
| `agentcore validate` | Validate configuration |
|
||||
| `agentcore pause` / `resume` | Pause or resume a deployed agent |
|
||||
104
agentclaw/README.md
Normal file
104
agentclaw/README.md
Normal file
@@ -0,0 +1,104 @@
|
||||
# AgentCore Project
|
||||
|
||||
This project was created with the [AgentCore CLI](https://github.com/aws/agentcore-cli).
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
my-project/
|
||||
├── AGENTS.md # AI coding assistant context
|
||||
├── agentcore/
|
||||
│ ├── agentcore.json # Project config (agents, memories, credentials, gateways, evaluators)
|
||||
│ ├── aws-targets.json # Deployment targets (account + region)
|
||||
│ ├── .env.local # Secrets — API keys (gitignored)
|
||||
│ ├── .llm-context/ # TypeScript type definitions for AI assistants
|
||||
│ │ ├── agentcore.ts # AgentCoreProjectSpec types
|
||||
│ │ ├── aws-targets.ts # Deployment target types
|
||||
│ │ └── mcp.ts # Gateway and MCP tool types
|
||||
│ └── cdk/ # CDK infrastructure (@aws/agentcore-cdk)
|
||||
├── app/ # Agent application code
|
||||
└── evaluators/ # Custom evaluator code (if any)
|
||||
```
|
||||
|
||||
## Getting Started
|
||||
|
||||
### Prerequisites
|
||||
|
||||
- **Node.js** 20.x or later
|
||||
- **Python 3.10+** and **uv** for Python agents ([install uv](https://docs.astral.sh/uv/getting-started/installation/))
|
||||
- **AWS credentials** configured (`aws configure` or environment variables)
|
||||
- **Docker** (only for Container build agents)
|
||||
|
||||
### Development
|
||||
|
||||
Run your agent locally:
|
||||
|
||||
```bash
|
||||
agentcore dev
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
Deploy to AWS:
|
||||
|
||||
```bash
|
||||
agentcore deploy
|
||||
```
|
||||
|
||||
## Commands
|
||||
|
||||
| Command | Description |
|
||||
| --- | --- |
|
||||
| `agentcore create` | Create a new AgentCore project |
|
||||
| `agentcore add` | Add resources (agent, memory, credential, gateway, evaluator, policy) |
|
||||
| `agentcore remove` | Remove resources |
|
||||
| `agentcore dev` | Run agent locally with hot-reload |
|
||||
| `agentcore deploy` | Deploy to AWS via CDK |
|
||||
| `agentcore status` | Show deployment status |
|
||||
| `agentcore invoke` | Invoke agent (local or deployed) |
|
||||
| `agentcore logs` | View agent logs |
|
||||
| `agentcore traces` | View agent traces |
|
||||
| `agentcore eval` | Run evaluations |
|
||||
| `agentcore package` | Package agent artifacts |
|
||||
| `agentcore validate` | Validate configuration |
|
||||
| `agentcore pause` | Pause a deployed agent |
|
||||
| `agentcore resume` | Resume a paused agent |
|
||||
| `agentcore fetch` | Fetch remote resource definitions |
|
||||
| `agentcore import` | Import existing resources |
|
||||
| `agentcore update` | Check for CLI updates |
|
||||
|
||||
## Configuration
|
||||
|
||||
Edit the JSON files in `agentcore/` to configure your project. See `agentcore/.llm-context/` for type definitions and validation constraints.
|
||||
|
||||
The project uses a **flat resource model** — agents, memories, credentials, gateways, evaluators, and policies are top-level arrays in `agentcore.json`. Resources are independent; agents discover memories and credentials at runtime via environment variables or SDK calls.
|
||||
|
||||
## Resources
|
||||
|
||||
| Resource | Purpose |
|
||||
| --- | --- |
|
||||
| Agent (runtime) | HTTP, MCP, or A2A agent deployed to AgentCore Runtime |
|
||||
| Memory | Persistent context storage with configurable strategies |
|
||||
| Credential | API key or OAuth credential providers |
|
||||
| Gateway | MCP gateway that routes tool calls to targets |
|
||||
| Gateway Target | Tool implementation (Lambda, MCP server, OpenAPI, Smithy, API Gateway) |
|
||||
| Evaluator | Custom LLM-as-a-Judge or code-based evaluation |
|
||||
| Online Eval Config | Continuous evaluation pipeline for deployed agents |
|
||||
| Policy | Cedar authorization policies for gateway tools |
|
||||
|
||||
### Agent Types
|
||||
|
||||
- **Template agents**: Created from framework templates (Strands, LangChain/LangGraph, GoogleADK, OpenAI Agents, Autogen)
|
||||
- **BYO agents**: Bring your own code with `agentcore add agent --type byo`
|
||||
- **Import agents**: Import existing Bedrock agents with `agentcore import`
|
||||
|
||||
### Build Types
|
||||
|
||||
- **CodeZip**: Python source packaged as a zip and deployed directly to AgentCore Runtime
|
||||
- **Container**: Docker image built via CodeBuild (ARM64), pushed to ECR, and deployed to AgentCore Runtime
|
||||
|
||||
## Documentation
|
||||
|
||||
- [AgentCore CLI](https://github.com/aws/agentcore-cli)
|
||||
- [AgentCore CDK Constructs](https://github.com/aws/agentcore-l3-cdk-constructs)
|
||||
- [Amazon Bedrock AgentCore](https://aws.amazon.com/bedrock/agentcore/)
|
||||
22
agentclaw/agentcore/.cli/deployed-state.json
Normal file
22
agentclaw/agentcore/.cli/deployed-state.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"targets": {
|
||||
"default": {
|
||||
"resources": {
|
||||
"runtimes": {
|
||||
"agent_claw_main": {
|
||||
"runtimeId": "agentclaw_agent_claw_main-vTRGIEG6ON",
|
||||
"runtimeArn": "arn:aws:bedrock-agentcore:us-east-1:495395224548:runtime/agentclaw_agent_claw_main-vTRGIEG6ON",
|
||||
"roleArn": "arn:aws:iam::495395224548:role/AgentCore-agentclaw-defau-ApplicationAgentAgentClaw-Ttg8kEtQ3cJj"
|
||||
}
|
||||
},
|
||||
"memories": {
|
||||
"AgentClawMemory": {
|
||||
"memoryId": "agentclaw_AgentClawMemory-i7Csf776AH",
|
||||
"memoryArn": "arn:aws:bedrock-agentcore:us-east-1:495395224548:memory/agentclaw_AgentClawMemory-i7Csf776AH"
|
||||
}
|
||||
},
|
||||
"stackName": "AgentCore-agentclaw-default"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
15
agentclaw/agentcore/.gitignore
vendored
Normal file
15
agentclaw/agentcore/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
# Secrets (local environment files are never committed)
|
||||
.env.local
|
||||
|
||||
# CDK Build Artifacts
|
||||
cdk/cdk.out/
|
||||
cdk/node_modules/
|
||||
|
||||
# CLI Internals
|
||||
.cli/*
|
||||
|
||||
# Ephemeral Staging
|
||||
.cache/*
|
||||
|
||||
# Exception: Commit the State
|
||||
!.cli/deployed-state.json
|
||||
16
agentclaw/agentcore/.llm-context/README.md
Normal file
16
agentclaw/agentcore/.llm-context/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# LLM Context Files
|
||||
|
||||
**DO NOT EDIT THESE FILES** - They are read-only reference for AI coding assistants.
|
||||
|
||||
## Files
|
||||
|
||||
| File | JSON Config | Purpose |
|
||||
| ---------------- | ------------------ | ----------------------------------------- |
|
||||
| `agentcore.ts` | `agentcore.json` | Project, agent, memory, credential config |
|
||||
| `mcp.ts` | `agentcore.json` | Gateways, targets, MCP runtime tools |
|
||||
| `aws-targets.ts` | `aws-targets.json` | Deployment targets (account + region) |
|
||||
|
||||
## Usage
|
||||
|
||||
When editing schema JSON files, reference the corresponding `.ts` file here for type definitions and validation
|
||||
constraints (marked with `@regex`, `@min`, `@max`).
|
||||
403
agentclaw/agentcore/.llm-context/agentcore.ts
Normal file
403
agentclaw/agentcore/.llm-context/agentcore.ts
Normal file
@@ -0,0 +1,403 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/**
|
||||
* READ-ONLY LLM CONTEXT - Do not edit this file.
|
||||
*
|
||||
* JSON File: agentcore/agentcore.json
|
||||
* Purpose: Top-level project configuration with flat resource model
|
||||
*/
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ROOT SCHEMA: AgentCoreProjectSpec
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface AgentCoreProjectSpec {
|
||||
name: string; // @regex ^[A-Za-z][A-Za-z0-9]{0,22}$ @max 23 - project name
|
||||
version: number; // Schema version (integer)
|
||||
managedBy: 'CDK'; // Enum — infrastructure manager. Default: "CDK"
|
||||
tags?: Record<string, string>;
|
||||
runtimes: AgentEnvSpec[]; // Unique by name
|
||||
memories: Memory[]; // Unique by name
|
||||
credentials: Credential[]; // Unique by name
|
||||
evaluators: Evaluator[]; // Unique by name — custom evaluator definitions
|
||||
onlineEvalConfigs: OnlineEvalConfig[]; // Unique by name — online evaluation configs
|
||||
agentCoreGateways: AgentCoreGateway[]; // Unique by name — MCP gateways
|
||||
mcpRuntimeTools?: AgentCoreMcpRuntimeTool[]; // Unique by name — standalone MCP runtime tools (not behind a gateway)
|
||||
unassignedTargets?: AgentCoreGatewayTarget[]; // Unique by name — targets not yet assigned to a gateway
|
||||
policyEngines: PolicyEngine[]; // Unique by name — Cedar policy engines
|
||||
configBundles: ConfigBundle[]; // Unique by name — configuration bundles for versioned config
|
||||
abTests: ABTest[]; // Unique by name — A/B test experiments
|
||||
/** @internal Auto-managed by AB test creation. Do not configure directly. */
|
||||
httpGateways: HttpGateway[]; // Unique by name — HTTP gateways bound to a runtime
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ENUMS
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
type BuildType = 'CodeZip' | 'Container';
|
||||
type PythonRuntime = 'PYTHON_3_10' | 'PYTHON_3_11' | 'PYTHON_3_12' | 'PYTHON_3_13' | 'PYTHON_3_14';
|
||||
type NodeRuntime = 'NODE_18' | 'NODE_20' | 'NODE_22';
|
||||
type RuntimeVersion = PythonRuntime | NodeRuntime;
|
||||
type NetworkMode = 'PUBLIC' | 'VPC';
|
||||
interface NetworkConfig {
|
||||
subnets: string[]; // subnet-xxx IDs
|
||||
securityGroups: string[]; // sg-xxx IDs
|
||||
}
|
||||
|
||||
type MemoryStrategyType = 'SEMANTIC' | 'SUMMARIZATION' | 'USER_PREFERENCE' | 'EPISODIC';
|
||||
type ModelProvider = 'Bedrock' | 'Gemini' | 'OpenAI' | 'Anthropic';
|
||||
type EvaluationLevel = 'SESSION' | 'TRACE' | 'TOOL_CALL';
|
||||
type GatewayTargetType = 'lambda' | 'mcpServer' | 'openApiSchema' | 'smithyModel' | 'apiGateway' | 'lambdaFunctionArn';
|
||||
type OutboundAuthType = 'OAUTH' | 'API_KEY' | 'NONE';
|
||||
type GatewayAuthorizerType = 'NONE' | 'AWS_IAM' | 'CUSTOM_JWT';
|
||||
type GatewayExceptionLevel = 'NONE' | 'DEBUG';
|
||||
type PolicyEngineMode = 'LOG_ONLY' | 'ENFORCE';
|
||||
type ValidationMode = 'FAIL_ON_ANY_FINDINGS' | 'IGNORE_ALL_FINDINGS';
|
||||
type ComputeHost = 'Lambda' | 'AgentCoreRuntime';
|
||||
type ABTestVariantName = 'C' | 'T1';
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AGENT
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
type ProtocolMode = 'HTTP' | 'MCP' | 'A2A' | 'AGUI';
|
||||
|
||||
interface AgentEnvSpec {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
build: BuildType;
|
||||
entrypoint: string; // @regex ^[a-zA-Z0-9_][a-zA-Z0-9_/.-]*\.(py|ts|js)(:[a-zA-Z_][a-zA-Z0-9_]*)?$ e.g. "main.py:handler" or "index.ts"
|
||||
codeLocation: string; // Directory path
|
||||
dockerfile?: string; // Custom Dockerfile name for Container builds (default: 'Dockerfile'). Must be a filename, not a path.
|
||||
runtimeVersion?: RuntimeVersion;
|
||||
envVars?: EnvVar[];
|
||||
networkMode?: NetworkMode; // default 'PUBLIC'
|
||||
networkConfig?: NetworkConfig; // Required when networkMode is 'VPC'
|
||||
instrumentation?: Instrumentation; // OTel settings
|
||||
protocol?: ProtocolMode; // default 'HTTP'
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface Instrumentation {
|
||||
enableOtel: boolean; // default true - wrap entrypoint with opentelemetry-instrument
|
||||
}
|
||||
|
||||
interface EnvVar {
|
||||
name: string; // @regex ^[A-Za-z_][A-Za-z0-9_]*$ @max 255
|
||||
value: string;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MEMORY
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface Memory {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
eventExpiryDuration: number; // @min 3 @max 365 (days)
|
||||
strategies: MemoryStrategy[]; // Unique by type. Can be empty (short-term memory).
|
||||
tags?: Record<string, string>;
|
||||
encryptionKeyArn?: string;
|
||||
executionRoleArn?: string;
|
||||
}
|
||||
|
||||
interface MemoryStrategy {
|
||||
type: MemoryStrategyType;
|
||||
name?: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
description?: string;
|
||||
namespaces?: string[];
|
||||
reflectionNamespaces?: string[]; // EPISODIC only: namespaces for cross-episode reflections
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CREDENTIAL
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface Credential {
|
||||
authorizerType: 'ApiKeyCredentialProvider' | 'OAuthCredentialProvider';
|
||||
name: string; // @regex ^[a-zA-Z0-9\-_]+$ @min 1 @max 128
|
||||
// Additional fields for OAuthCredentialProvider:
|
||||
discoveryUrl?: string; // OIDC discovery URL (OAuth only)
|
||||
scopes?: string[]; // Supported scopes (OAuth only)
|
||||
vendor?: string; // Credential provider vendor type (OAuth only, default: 'CustomOauth2')
|
||||
managed?: boolean; // Whether auto-created by CLI (OAuth only)
|
||||
usage?: 'inbound' | 'outbound'; // Auth direction (OAuth only)
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// EVALUATOR
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface Evaluator {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
level: EvaluationLevel;
|
||||
description?: string;
|
||||
config: EvaluatorConfig; // Must have either llmAsAJudge or codeBased, not both
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface EvaluatorConfig {
|
||||
llmAsAJudge?: LlmAsAJudgeConfig;
|
||||
codeBased?: CodeBasedConfig;
|
||||
}
|
||||
|
||||
interface LlmAsAJudgeConfig {
|
||||
model: string; // Bedrock model ID or ARN
|
||||
instructions: string; // Evaluation instructions
|
||||
ratingScale: RatingScale; // Must have either numerical or categorical, not both
|
||||
}
|
||||
|
||||
interface RatingScale {
|
||||
numerical?: { value: number; label: string; definition: string }[];
|
||||
categorical?: { label: string; definition: string }[];
|
||||
}
|
||||
|
||||
interface CodeBasedConfig {
|
||||
managed?: ManagedCodeBasedConfig;
|
||||
external?: ExternalCodeBasedConfig;
|
||||
}
|
||||
|
||||
interface ManagedCodeBasedConfig {
|
||||
codeLocation: string;
|
||||
entrypoint: string; // default 'lambda_function.handler'
|
||||
timeoutSeconds: number; // @min 1 @max 300 (default 60)
|
||||
additionalPolicies?: string[];
|
||||
}
|
||||
|
||||
interface ExternalCodeBasedConfig {
|
||||
lambdaArn: string; // @regex ^arn:aws[a-z-]*:lambda:[a-z0-9-]+:\d{12}:function:.+$
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ONLINE EVAL CONFIG
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface OnlineEvalConfig {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
agent: string; // Agent name — must match a project agent
|
||||
evaluators: string[]; // @min 1 — evaluator names, Builtin.* IDs, or evaluator ARNs
|
||||
samplingRate: number; // @min 0.01 @max 100 (percentage)
|
||||
description?: string; // @max 200
|
||||
enableOnCreate?: boolean; // Whether to enable on create (default: true)
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// GATEWAY (MCP)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface AgentCoreGateway {
|
||||
name: string; // @regex ^[0-9a-zA-Z](?:[0-9a-zA-Z-]*[0-9a-zA-Z])?$ @max 100
|
||||
description?: string;
|
||||
targets: AgentCoreGatewayTarget[]; // Gateway targets
|
||||
authorizerType?: GatewayAuthorizerType; // default 'NONE'
|
||||
authorizerConfiguration?: AuthorizerConfig; // Required when authorizerType is 'CUSTOM_JWT'
|
||||
enableSemanticSearch?: boolean; // default true
|
||||
exceptionLevel?: GatewayExceptionLevel; // default 'NONE'
|
||||
policyEngineConfiguration?: GatewayPolicyEngineConfiguration;
|
||||
tags?: Record<string, string>;
|
||||
}
|
||||
|
||||
interface AuthorizerConfig {
|
||||
customJwtAuthorizer?: {
|
||||
discoveryUrl: string; // OIDC discovery URL (HTTPS, must end with /.well-known/openid-configuration)
|
||||
allowedAudience?: string[];
|
||||
allowedClients?: string[];
|
||||
allowedScopes?: string[];
|
||||
customClaims?: CustomClaimValidation[];
|
||||
};
|
||||
}
|
||||
|
||||
interface CustomClaimValidation {
|
||||
inboundTokenClaimName: string; // @regex ^[A-Za-z0-9_.:-]+$ @max 255
|
||||
inboundTokenClaimValueType: 'STRING' | 'STRING_ARRAY';
|
||||
authorizingClaimMatchValue: {
|
||||
claimMatchOperator: 'EQUALS' | 'CONTAINS' | 'CONTAINS_ANY';
|
||||
claimMatchValue: {
|
||||
matchValueString?: string; // @regex ^[A-Za-z0-9_.-]+$ @max 255
|
||||
matchValueStringList?: string[]; // each @regex ^[A-Za-z0-9_.-]+$ @max 255
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
interface GatewayPolicyEngineConfiguration {
|
||||
policyEngineName: string; // Reference to a PolicyEngine name
|
||||
mode: PolicyEngineMode;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// GATEWAY TARGET
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface AgentCoreGatewayTarget {
|
||||
name: string;
|
||||
targetType: GatewayTargetType;
|
||||
toolDefinitions?: ToolDefinition[]; // Required for 'lambda' targets
|
||||
compute?: ToolComputeConfig; // Required for 'lambda' and scaffold targets
|
||||
endpoint?: string; // URL — required for external 'mcpServer' targets
|
||||
outboundAuth?: OutboundAuth;
|
||||
apiGateway?: ApiGatewayConfig; // Required for 'apiGateway' target type
|
||||
schemaSource?: SchemaSource; // Required for 'openApiSchema' / 'smithyModel' targets
|
||||
lambdaFunctionArn?: LambdaFunctionArnConfig; // Required for 'lambdaFunctionArn' target type
|
||||
}
|
||||
|
||||
interface OutboundAuth {
|
||||
type: OutboundAuthType; // default 'NONE'
|
||||
credentialName?: string; // Required when type is not 'NONE'
|
||||
scopes?: string[];
|
||||
}
|
||||
|
||||
interface ToolDefinition {
|
||||
name: string;
|
||||
description?: string;
|
||||
inputSchema: object; // JSON Schema
|
||||
outputSchema?: object;
|
||||
}
|
||||
|
||||
interface ToolComputeConfig {
|
||||
host: ComputeHost;
|
||||
implementation: ToolImplementationBinding;
|
||||
// Lambda-specific:
|
||||
nodeVersion?: NodeRuntime; // Required for TypeScript Lambda
|
||||
pythonVersion?: PythonRuntime; // Required for Python Lambda
|
||||
timeout?: number; // @min 1 @max 900
|
||||
memorySize?: number; // @min 128 @max 10240
|
||||
iamPolicy?: object; // IAM policy document
|
||||
// AgentCoreRuntime-specific:
|
||||
runtime?: RuntimeConfig;
|
||||
}
|
||||
|
||||
interface ToolImplementationBinding {
|
||||
language: 'TypeScript' | 'Python';
|
||||
path: string;
|
||||
handler: string;
|
||||
}
|
||||
|
||||
interface RuntimeConfig {
|
||||
artifact: 'CodeZip';
|
||||
pythonVersion: PythonRuntime;
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
entrypoint: string; // Python file path with optional handler
|
||||
codeLocation: string;
|
||||
instrumentation?: Instrumentation;
|
||||
networkMode?: NetworkMode; // default 'PUBLIC'
|
||||
description?: string;
|
||||
}
|
||||
|
||||
interface ApiGatewayConfig {
|
||||
restApiId: string;
|
||||
stage: string;
|
||||
apiGatewayToolConfiguration: {
|
||||
toolFilters: {
|
||||
filterPath: string;
|
||||
methods: ('GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH' | 'HEAD' | 'OPTIONS')[];
|
||||
}[];
|
||||
toolOverrides?: { name: string; path: string; method: string; description?: string }[];
|
||||
};
|
||||
}
|
||||
|
||||
interface LambdaFunctionArnConfig {
|
||||
lambdaArn: string; // @max 170
|
||||
toolSchemaFile: string;
|
||||
}
|
||||
|
||||
type SchemaSource = { inline: { path: string } } | { s3: { uri: string; bucketOwnerAccountId?: string } };
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// MCP RUNTIME TOOL
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface AgentCoreMcpRuntimeTool {
|
||||
name: string;
|
||||
toolDefinition: ToolDefinition;
|
||||
compute: {
|
||||
host: 'AgentCoreRuntime'; // Only AgentCoreRuntime (Python only)
|
||||
implementation: ToolImplementationBinding;
|
||||
runtime?: RuntimeConfig;
|
||||
iamPolicy?: object;
|
||||
};
|
||||
bindings?: McpRuntimeBinding[]; // Grant agents permission to invoke this tool
|
||||
}
|
||||
|
||||
interface McpRuntimeBinding {
|
||||
runtimeName: string; // Agent runtime name to bind to
|
||||
envVarName: string; // @regex ^[A-Za-z_][A-Za-z0-9_]*$ — env var for runtime ARN
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// POLICY ENGINE
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface PolicyEngine {
|
||||
name: string; // @regex ^[A-Za-z][A-Za-z0-9_]{0,47}$ @max 48
|
||||
description?: string; // @max 4096
|
||||
encryptionKeyArn?: string;
|
||||
tags?: Record<string, string>;
|
||||
policies: Policy[]; // Unique by name
|
||||
}
|
||||
|
||||
interface Policy {
|
||||
name: string; // @regex ^[A-Za-z][A-Za-z0-9_]{0,47}$ @max 48
|
||||
description?: string; // @max 4096
|
||||
statement: string; // Cedar policy statement
|
||||
sourceFile?: string;
|
||||
validationMode: ValidationMode; // default 'FAIL_ON_ANY_FINDINGS'
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// CONFIG BUNDLE
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface ConfigBundle {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,99}$ @max 100
|
||||
description?: string; // @max 500
|
||||
/** Component configurations keyed by component ARN or placeholder (e.g. {{runtime:<runtimeName>}}) */
|
||||
components: Record<string, ComponentConfiguration>;
|
||||
branchName?: string; // @max 128 — optional branch name for versioning
|
||||
commitMessage?: string; // @max 500 — optional commit message
|
||||
}
|
||||
|
||||
interface ComponentConfiguration {
|
||||
configuration: Record<string, unknown>; // Freeform configuration for the component
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// AB TEST
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
interface ABTest {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_]{0,47}$ @max 48
|
||||
description?: string; // @max 200
|
||||
gatewayRef: string; // Reference to the gateway (ARN or {{gateway:name}} placeholder)
|
||||
roleArn?: string;
|
||||
variants: [ABTestVariant, ABTestVariant]; // Exactly 2 — one 'C' (control) and one 'T1' (treatment). Weights must sum to 100.
|
||||
evaluationConfig: {
|
||||
onlineEvaluationConfigArn: string;
|
||||
};
|
||||
trafficAllocationConfig?: {
|
||||
routeOnHeader: { headerName: string };
|
||||
};
|
||||
maxDurationDays?: number; // @min 1 @max 90
|
||||
enableOnCreate?: boolean;
|
||||
}
|
||||
|
||||
interface ABTestVariant {
|
||||
name: ABTestVariantName;
|
||||
weight: number; // @min 1 @max 100
|
||||
variantConfiguration: {
|
||||
configurationBundle: {
|
||||
bundleArn: string;
|
||||
bundleVersion: string;
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// HTTP GATEWAY
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
/** @internal HTTP gateway auto-created when setting up an AB test. */
|
||||
interface HttpGateway {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9-]{0,47}$ @max 48
|
||||
description?: string; // @max 200
|
||||
runtimeRef: string; // Reference to a runtime name from spec.runtimes
|
||||
roleArn?: string; // IAM role ARN — auto-created if omitted
|
||||
}
|
||||
45
agentclaw/agentcore/.llm-context/aws-targets.ts
Normal file
45
agentclaw/agentcore/.llm-context/aws-targets.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/**
|
||||
* READ-ONLY LLM CONTEXT - Do not edit this file.
|
||||
*
|
||||
* JSON File: agentcore/aws-targets.json
|
||||
* Purpose: AWS deployment targets for AgentCore resources
|
||||
*/
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// ROOT SCHEMA: AwsDeploymentTargets (array)
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
// The JSON file contains an array of deployment targets.
|
||||
// Target names must be unique within the array.
|
||||
type AwsDeploymentTargets = AwsDeploymentTarget[];
|
||||
|
||||
interface AwsDeploymentTarget {
|
||||
name: string; // @regex ^[a-zA-Z][a-zA-Z0-9_-]*$ @max 64 - unique identifier
|
||||
description?: string; // @max 256
|
||||
account: string; // @regex ^[0-9]{12}$ - AWS account ID (exactly 12 digits)
|
||||
region: AgentCoreRegion;
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
// SUPPORTED REGIONS
|
||||
// https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-regions.html
|
||||
// ─────────────────────────────────────────────────────────────────────────────
|
||||
|
||||
type AgentCoreRegion =
|
||||
| 'ap-northeast-1'
|
||||
| 'ap-northeast-2'
|
||||
| 'ap-south-1'
|
||||
| 'ap-southeast-1'
|
||||
| 'ap-southeast-2'
|
||||
| 'ca-central-1'
|
||||
| 'eu-central-1'
|
||||
| 'eu-north-1'
|
||||
| 'eu-west-1'
|
||||
| 'eu-west-2'
|
||||
| 'eu-west-3'
|
||||
| 'sa-east-1'
|
||||
| 'us-east-1'
|
||||
| 'us-east-2'
|
||||
| 'us-west-2'
|
||||
| 'us-gov-west-1';
|
||||
55
agentclaw/agentcore/agentcore.json
Normal file
55
agentclaw/agentcore/agentcore.json
Normal file
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"$schema": "https://schema.agentcore.aws.dev/v1/agentcore.json",
|
||||
"name": "agentclaw",
|
||||
"version": 1,
|
||||
"managedBy": "CDK",
|
||||
"tags": {
|
||||
"agentcore:created-by": "agentcore-cli",
|
||||
"agentcore:project-name": "agentclaw"
|
||||
},
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "agent_claw_main",
|
||||
"build": "CodeZip",
|
||||
"entrypoint": "main.py",
|
||||
"codeLocation": "app/agent_claw_main/",
|
||||
"runtimeVersion": "PYTHON_3_14",
|
||||
"networkMode": "PUBLIC",
|
||||
"protocol": "HTTP"
|
||||
}
|
||||
],
|
||||
"memories": [
|
||||
{
|
||||
"name": "AgentClawMemory",
|
||||
"eventExpiryDuration": 30,
|
||||
"strategies": [
|
||||
{
|
||||
"type": "SEMANTIC",
|
||||
"namespaces": [
|
||||
"/users/{actorId}/facts"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "SUMMARIZATION",
|
||||
"namespaces": [
|
||||
"/summaries/{actorId}/{sessionId}"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "USER_PREFERENCE",
|
||||
"namespaces": [
|
||||
"/users/{actorId}/preferences"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"credentials": [],
|
||||
"evaluators": [],
|
||||
"onlineEvalConfigs": [],
|
||||
"agentCoreGateways": [],
|
||||
"policyEngines": [],
|
||||
"configBundles": [],
|
||||
"abTests": [],
|
||||
"httpGateways": []
|
||||
}
|
||||
7
agentclaw/agentcore/aws-targets.json
Normal file
7
agentclaw/agentcore/aws-targets.json
Normal file
@@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"name": "default",
|
||||
"account": "495395224548",
|
||||
"region": "us-east-1"
|
||||
}
|
||||
]
|
||||
9
agentclaw/agentcore/cdk/.gitignore
vendored
Normal file
9
agentclaw/agentcore/cdk/.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
# Build output
|
||||
dist/
|
||||
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
6
agentclaw/agentcore/cdk/.npmignore
Normal file
6
agentclaw/agentcore/cdk/.npmignore
Normal file
@@ -0,0 +1,6 @@
|
||||
*.ts
|
||||
!*.d.ts
|
||||
|
||||
# CDK asset staging directory
|
||||
.cdk.staging
|
||||
cdk.out
|
||||
8
agentclaw/agentcore/cdk/.prettierrc
Normal file
8
agentclaw/agentcore/cdk/.prettierrc
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"trailingComma": "es5",
|
||||
"printWidth": 120,
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true,
|
||||
"arrowParens": "avoid"
|
||||
}
|
||||
26
agentclaw/agentcore/cdk/README.md
Normal file
26
agentclaw/agentcore/cdk/README.md
Normal file
@@ -0,0 +1,26 @@
|
||||
# AgentCore CDK Project
|
||||
|
||||
This CDK project is managed by the AgentCore CLI. It deploys your agent infrastructure into AWS using the `@aws/agentcore-cdk` L3 constructs.
|
||||
|
||||
## Structure
|
||||
|
||||
- `bin/cdk.ts` — Entry point. Reads project configuration from `agentcore/` and creates a stack per deployment target.
|
||||
- `lib/cdk-stack.ts` — Defines `AgentCoreStack`, which wraps the `AgentCoreApplication` L3 construct.
|
||||
- `test/cdk.test.ts` — Unit tests for stack synthesis.
|
||||
|
||||
## Useful commands
|
||||
|
||||
- `npm run build` compile TypeScript to JavaScript
|
||||
- `npm run test` run unit tests
|
||||
- `npx cdk synth` emit the synthesized CloudFormation template
|
||||
- `npx cdk deploy` deploy this stack to your default AWS account/region
|
||||
- `npx cdk diff` compare deployed stack with current state
|
||||
|
||||
## Usage
|
||||
|
||||
You typically don't need to interact with this directory directly. The AgentCore CLI handles synthesis and deployment:
|
||||
|
||||
```bash
|
||||
agentcore deploy # synthesizes and deploys via CDK
|
||||
agentcore status # checks deployment status
|
||||
```
|
||||
91
agentclaw/agentcore/cdk/bin/cdk.ts
Normal file
91
agentclaw/agentcore/cdk/bin/cdk.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env node
|
||||
import { AgentCoreStack } from '../lib/cdk-stack';
|
||||
import { ConfigIO, type AwsDeploymentTarget } from '@aws/agentcore-cdk';
|
||||
import { App, type Environment } from 'aws-cdk-lib';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
function toEnvironment(target: AwsDeploymentTarget): Environment {
|
||||
return {
|
||||
account: target.account,
|
||||
region: target.region,
|
||||
};
|
||||
}
|
||||
|
||||
function sanitize(name: string): string {
|
||||
return name.replace(/_/g, '-');
|
||||
}
|
||||
|
||||
function toStackName(projectName: string, targetName: string): string {
|
||||
return `AgentCore-${sanitize(projectName)}-${sanitize(targetName)}`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
// Config root is parent of cdk/ directory. The CLI sets process.cwd() to agentcore/cdk/.
|
||||
const configRoot = path.resolve(process.cwd(), '..');
|
||||
const configIO = new ConfigIO({ baseDir: configRoot });
|
||||
|
||||
const spec = await configIO.readProjectSpec();
|
||||
const targets = await configIO.readAWSDeploymentTargets();
|
||||
|
||||
// Extract MCP configuration from project spec.
|
||||
// Gateway fields are stored in agentcore.json but may not yet be on the
|
||||
// AgentCoreProjectSpec type from @aws/agentcore-cdk, so we read them
|
||||
// dynamically and cast the resulting object.
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const specAny = spec as any;
|
||||
const mcpSpec = specAny.agentCoreGateways?.length
|
||||
? {
|
||||
agentCoreGateways: specAny.agentCoreGateways,
|
||||
mcpRuntimeTools: specAny.mcpRuntimeTools,
|
||||
unassignedTargets: specAny.unassignedTargets,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
// Read deployed state for credential ARNs (populated by pre-deploy identity setup)
|
||||
let deployedState: Record<string, unknown> | undefined;
|
||||
try {
|
||||
deployedState = JSON.parse(fs.readFileSync(path.join(configRoot, '.cli', 'deployed-state.json'), 'utf8'));
|
||||
} catch {
|
||||
// Deployed state may not exist on first deploy
|
||||
}
|
||||
|
||||
if (targets.length === 0) {
|
||||
throw new Error('No deployment targets configured. Please define targets in agentcore/aws-targets.json');
|
||||
}
|
||||
|
||||
const app = new App();
|
||||
|
||||
for (const target of targets) {
|
||||
const env = toEnvironment(target);
|
||||
const stackName = toStackName(spec.name, target.name);
|
||||
|
||||
// Extract credentials from deployed state for this target
|
||||
const targetState = (deployedState as Record<string, unknown>)?.targets as
|
||||
| Record<string, Record<string, unknown>>
|
||||
| undefined;
|
||||
const targetResources = targetState?.[target.name]?.resources as Record<string, unknown> | undefined;
|
||||
const credentials = targetResources?.credentials as
|
||||
| Record<string, { credentialProviderArn: string; clientSecretArn?: string }>
|
||||
| undefined;
|
||||
|
||||
new AgentCoreStack(app, stackName, {
|
||||
spec,
|
||||
mcpSpec,
|
||||
credentials,
|
||||
env,
|
||||
description: `AgentCore stack for ${spec.name} deployed to ${target.name} (${target.region})`,
|
||||
tags: {
|
||||
'agentcore:project-name': spec.name,
|
||||
'agentcore:target-name': target.name,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
app.synth();
|
||||
}
|
||||
|
||||
main().catch((error: unknown) => {
|
||||
console.error('AgentCore CDK synthesis failed:', error instanceof Error ? error.message : error);
|
||||
process.exitCode = 1;
|
||||
});
|
||||
88
agentclaw/agentcore/cdk/cdk.json
Normal file
88
agentclaw/agentcore/cdk/cdk.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"app": "node dist/bin/cdk.js",
|
||||
"watch": {
|
||||
"include": ["**"],
|
||||
"exclude": ["README.md", "cdk*.json", "tsconfig.json", "package*.json", "yarn.lock", "node_modules", "dist", "test"]
|
||||
},
|
||||
"context": {
|
||||
"@aws-cdk/aws-signer:signingProfileNamePassedToCfn": true,
|
||||
"@aws-cdk/aws-ecs-patterns:secGroupsDisablesImplicitOpenListener": true,
|
||||
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
|
||||
"@aws-cdk/core:checkSecretUsage": true,
|
||||
"@aws-cdk/core:target-partitions": ["aws", "aws-cn", "aws-us-gov"],
|
||||
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
|
||||
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
|
||||
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
|
||||
"@aws-cdk/aws-iam:minimizePolicies": true,
|
||||
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
|
||||
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
|
||||
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
|
||||
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
|
||||
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
|
||||
"@aws-cdk/core:enablePartitionLiterals": true,
|
||||
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
|
||||
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
|
||||
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
|
||||
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
|
||||
"@aws-cdk/aws-route53-patters:useCertificate": true,
|
||||
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
|
||||
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
|
||||
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
|
||||
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
|
||||
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
|
||||
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
|
||||
"@aws-cdk/aws-redshift:columnId": true,
|
||||
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
|
||||
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
|
||||
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
|
||||
"@aws-cdk/aws-kms:aliasNameRef": true,
|
||||
"@aws-cdk/aws-kms:applyImportedAliasPermissionsToPrincipal": true,
|
||||
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
|
||||
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
|
||||
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
|
||||
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
|
||||
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
|
||||
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
|
||||
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
|
||||
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
|
||||
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
|
||||
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true,
|
||||
"@aws-cdk/aws-cloudwatch-actions:changeLambdaPermissionLogicalIdForLambdaAction": true,
|
||||
"@aws-cdk/aws-codepipeline:crossAccountKeysDefaultValueToFalse": true,
|
||||
"@aws-cdk/aws-codepipeline:defaultPipelineTypeToV2": true,
|
||||
"@aws-cdk/aws-kms:reduceCrossAccountRegionPolicyScope": true,
|
||||
"@aws-cdk/aws-eks:nodegroupNameAttribute": true,
|
||||
"@aws-cdk/aws-ec2:ebsDefaultGp3Volume": true,
|
||||
"@aws-cdk/aws-ecs:removeDefaultDeploymentAlarm": true,
|
||||
"@aws-cdk/custom-resources:logApiResponseDataPropertyTrueDefault": false,
|
||||
"@aws-cdk/aws-s3:keepNotificationInImportedBucket": false,
|
||||
"@aws-cdk/core:explicitStackTags": true,
|
||||
"@aws-cdk/aws-ecs:enableImdsBlockingDeprecatedFeature": false,
|
||||
"@aws-cdk/aws-ecs:disableEcsImdsBlocking": true,
|
||||
"@aws-cdk/aws-ecs:reduceEc2FargateCloudWatchPermissions": true,
|
||||
"@aws-cdk/aws-dynamodb:resourcePolicyPerReplica": true,
|
||||
"@aws-cdk/aws-ec2:ec2SumTImeoutEnabled": true,
|
||||
"@aws-cdk/aws-appsync:appSyncGraphQLAPIScopeLambdaPermission": true,
|
||||
"@aws-cdk/aws-rds:setCorrectValueForDatabaseInstanceReadReplicaInstanceResourceId": true,
|
||||
"@aws-cdk/core:cfnIncludeRejectComplexResourceUpdateCreatePolicyIntrinsics": true,
|
||||
"@aws-cdk/aws-lambda-nodejs:sdkV3ExcludeSmithyPackages": true,
|
||||
"@aws-cdk/aws-stepfunctions-tasks:fixRunEcsTaskPolicy": true,
|
||||
"@aws-cdk/aws-ec2:bastionHostUseAmazonLinux2023ByDefault": true,
|
||||
"@aws-cdk/aws-route53-targets:userPoolDomainNameMethodWithoutCustomResource": true,
|
||||
"@aws-cdk/aws-elasticloadbalancingV2:albDualstackWithoutPublicIpv4SecurityGroupRulesDefault": true,
|
||||
"@aws-cdk/aws-iam:oidcRejectUnauthorizedConnections": true,
|
||||
"@aws-cdk/core:enableAdditionalMetadataCollection": true,
|
||||
"@aws-cdk/aws-lambda:createNewPoliciesWithAddToRolePolicy": false,
|
||||
"@aws-cdk/aws-s3:setUniqueReplicationRoleName": true,
|
||||
"@aws-cdk/aws-events:requireEventBusPolicySid": true,
|
||||
"@aws-cdk/core:aspectPrioritiesMutating": true,
|
||||
"@aws-cdk/aws-dynamodb:retainTableReplica": true,
|
||||
"@aws-cdk/aws-stepfunctions:useDistributedMapResultWriterV2": true,
|
||||
"@aws-cdk/s3-notifications:addS3TrustKeyPolicyForSnsSubscriptions": true,
|
||||
"@aws-cdk/aws-ec2:requirePrivateSubnetsForEgressOnlyInternetGateway": true,
|
||||
"@aws-cdk/aws-s3:publicAccessBlockedByDefault": true,
|
||||
"@aws-cdk/aws-lambda:useCdkManagedLogGroup": true,
|
||||
"@aws-cdk/aws-elasticloadbalancingv2:networkLoadBalancerWithSecurityGroupByDefault": true,
|
||||
"@aws-cdk/aws-ecs-patterns:uniqueTargetGroupId": true
|
||||
}
|
||||
}
|
||||
9
agentclaw/agentcore/cdk/jest.config.js
Normal file
9
agentclaw/agentcore/cdk/jest.config.js
Normal file
@@ -0,0 +1,9 @@
|
||||
module.exports = {
|
||||
testEnvironment: 'node',
|
||||
roots: ['<rootDir>/test'],
|
||||
testMatch: ['**/*.test.ts'],
|
||||
transform: {
|
||||
'^.+\\.tsx?$': 'ts-jest',
|
||||
},
|
||||
setupFilesAfterEnv: ['aws-cdk-lib/testhelpers/jest-autoclean'],
|
||||
};
|
||||
62
agentclaw/agentcore/cdk/lib/cdk-stack.ts
Normal file
62
agentclaw/agentcore/cdk/lib/cdk-stack.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
AgentCoreApplication,
|
||||
AgentCoreMcp,
|
||||
type AgentCoreProjectSpec,
|
||||
type AgentCoreMcpSpec,
|
||||
} from '@aws/agentcore-cdk';
|
||||
import { CfnOutput, Stack, type StackProps } from 'aws-cdk-lib';
|
||||
import { Construct } from 'constructs';
|
||||
|
||||
export interface AgentCoreStackProps extends StackProps {
|
||||
/**
|
||||
* The AgentCore project specification containing agents, memories, and credentials.
|
||||
*/
|
||||
spec: AgentCoreProjectSpec;
|
||||
/**
|
||||
* The MCP specification containing gateways and servers.
|
||||
*/
|
||||
mcpSpec?: AgentCoreMcpSpec;
|
||||
/**
|
||||
* Credential provider ARNs from deployed state, keyed by credential name.
|
||||
*/
|
||||
credentials?: Record<string, { credentialProviderArn: string; clientSecretArn?: string }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* CDK Stack that deploys AgentCore infrastructure.
|
||||
*
|
||||
* This is a thin wrapper that instantiates L3 constructs.
|
||||
* All resource logic and outputs are contained within the L3 constructs.
|
||||
*/
|
||||
export class AgentCoreStack extends Stack {
|
||||
/** The AgentCore application containing all agent environments */
|
||||
public readonly application: AgentCoreApplication;
|
||||
|
||||
constructor(scope: Construct, id: string, props: AgentCoreStackProps) {
|
||||
super(scope, id, props);
|
||||
|
||||
const { spec, mcpSpec, credentials } = props;
|
||||
|
||||
// Create AgentCoreApplication with all agents
|
||||
this.application = new AgentCoreApplication(this, 'Application', {
|
||||
spec,
|
||||
});
|
||||
|
||||
// Create AgentCoreMcp if there are gateways configured
|
||||
if (mcpSpec?.agentCoreGateways && mcpSpec.agentCoreGateways.length > 0) {
|
||||
new AgentCoreMcp(this, 'Mcp', {
|
||||
projectName: spec.name,
|
||||
mcpSpec,
|
||||
agentCoreApplication: this.application,
|
||||
credentials,
|
||||
projectTags: spec.tags,
|
||||
});
|
||||
}
|
||||
|
||||
// Stack-level output
|
||||
new CfnOutput(this, 'StackNameOutput', {
|
||||
description: 'Name of the CloudFormation Stack',
|
||||
value: this.stackName,
|
||||
});
|
||||
}
|
||||
}
|
||||
5772
agentclaw/agentcore/cdk/package-lock.json
generated
Normal file
5772
agentclaw/agentcore/cdk/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
30
agentclaw/agentcore/cdk/package.json
Normal file
30
agentclaw/agentcore/cdk/package.json
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"name": "agentcore-cdk-app",
|
||||
"version": "0.1.0",
|
||||
"bin": {
|
||||
"cdk": "dist/bin/cdk.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"watch": "tsc -w",
|
||||
"test": "jest",
|
||||
"cdk": "npm run build && cdk",
|
||||
"clean": "rm -rf dist",
|
||||
"format": "prettier --write .",
|
||||
"format:check": "prettier --check ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/node": "^24.10.1",
|
||||
"jest": "^29.7.0",
|
||||
"ts-jest": "^29.2.5",
|
||||
"aws-cdk": "2.1100.1",
|
||||
"prettier": "^3.4.2",
|
||||
"typescript": "~5.9.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws/agentcore-cdk": "^0.1.0-alpha.19",
|
||||
"aws-cdk-lib": "^2.248.0",
|
||||
"constructs": "^10.0.0"
|
||||
}
|
||||
}
|
||||
28
agentclaw/agentcore/cdk/test/cdk.test.ts
Normal file
28
agentclaw/agentcore/cdk/test/cdk.test.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import * as cdk from 'aws-cdk-lib';
|
||||
import { Template } from 'aws-cdk-lib/assertions';
|
||||
import { AgentCoreStack } from '../lib/cdk-stack';
|
||||
|
||||
test('AgentCoreStack synthesizes with empty spec', () => {
|
||||
const app = new cdk.App();
|
||||
const stack = new AgentCoreStack(app, 'TestStack', {
|
||||
spec: {
|
||||
name: 'testproject',
|
||||
version: 1,
|
||||
managedBy: 'CDK' as const,
|
||||
runtimes: [],
|
||||
memories: [],
|
||||
credentials: [],
|
||||
evaluators: [],
|
||||
onlineEvalConfigs: [],
|
||||
configBundles: [],
|
||||
policyEngines: [],
|
||||
agentCoreGateways: [],
|
||||
mcpRuntimeTools: [],
|
||||
unassignedTargets: [],
|
||||
},
|
||||
});
|
||||
const template = Template.fromStack(stack);
|
||||
template.hasOutput('StackNameOutput', {
|
||||
Description: 'Name of the CloudFormation Stack',
|
||||
});
|
||||
});
|
||||
28
agentclaw/agentcore/cdk/tsconfig.json
Normal file
28
agentclaw/agentcore/cdk/tsconfig.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "Node",
|
||||
"lib": ["es2022"],
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
"experimentalDecorators": true,
|
||||
"strictPropertyInitialization": true,
|
||||
"skipLibCheck": true,
|
||||
"typeRoots": ["./node_modules/@types"],
|
||||
"rootDir": ".",
|
||||
"outDir": "dist"
|
||||
},
|
||||
"include": ["bin/**/*", "lib/**/*", "test/**/*"],
|
||||
"exclude": ["node_modules", "cdk.out", "dist"]
|
||||
}
|
||||
41
agentclaw/app/agent_claw_main/.gitignore
vendored
Normal file
41
agentclaw/app/agent_claw_main/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
# Environment variables
|
||||
.env
|
||||
|
||||
# Python
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
*.so
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
*.swp
|
||||
*.swo
|
||||
*~
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
38
agentclaw/app/agent_claw_main/README.md
Normal file
38
agentclaw/app/agent_claw_main/README.md
Normal file
@@ -0,0 +1,38 @@
|
||||
This is a project generated by the AgentCore CLI!
|
||||
|
||||
# Layout
|
||||
|
||||
The generated application code lives at the agent root directory. At the root, there is a `.gitignore` file, an
|
||||
`agentcore/` folder which represents the configurations and state associated with this project. Other `agentcore`
|
||||
commands like `deploy`, `dev`, and `invoke` rely on the configuration stored here.
|
||||
|
||||
## Agent Root
|
||||
|
||||
The main entrypoint to your app is defined in `main.py`. Using the AgentCore SDK `@app.entrypoint` decorator, this
|
||||
file defines a Starlette ASGI app with the chosen Agent framework SDK running within.
|
||||
|
||||
`model/load.py` instantiates your chosen model provider.
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Variable | Required | Description |
|
||||
| --- | --- | --- |
|
||||
| `LOCAL_DEV` | No | Set to `1` to use `.env.local` instead of AgentCore Identity |
|
||||
|
||||
# Developing locally
|
||||
|
||||
If installation was successful, a virtual environment is already created with dependencies installed.
|
||||
|
||||
Run `source .venv/bin/activate` before developing.
|
||||
|
||||
`agentcore dev` will start a local server on 0.0.0.0:8080.
|
||||
|
||||
In a new terminal, you can invoke that server with:
|
||||
|
||||
`agentcore invoke --dev "What can you do"`
|
||||
|
||||
# Deployment
|
||||
|
||||
After providing credentials, `agentcore deploy` will deploy your project into Amazon Bedrock AgentCore.
|
||||
|
||||
Use `agentcore invoke` to invoke your deployed agent.
|
||||
4
agentclaw/app/agent_claw_main/channels/__init__.py
Normal file
4
agentclaw/app/agent_claw_main/channels/__init__.py
Normal file
@@ -0,0 +1,4 @@
|
||||
from .adapter import ChannelAdapter
|
||||
from .telegram import TelegramAdapter
|
||||
|
||||
__all__ = ['ChannelAdapter', 'TelegramAdapter']
|
||||
18
agentclaw/app/agent_claw_main/channels/adapter.py
Normal file
18
agentclaw/app/agent_claw_main/channels/adapter.py
Normal file
@@ -0,0 +1,18 @@
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class ChannelAdapter(Protocol):
|
||||
"""Protocol for channel-specific message delivery."""
|
||||
|
||||
def send(self, text: str) -> str:
|
||||
"""Send a message. Returns message_id if available."""
|
||||
...
|
||||
|
||||
def send_typing(self) -> None:
|
||||
"""Send a typing indicator (best-effort)."""
|
||||
...
|
||||
|
||||
def edit(self, message_id: str, text: str) -> None:
|
||||
"""Edit an existing message in-place."""
|
||||
...
|
||||
71
agentclaw/app/agent_claw_main/channels/telegram.py
Normal file
71
agentclaw/app/agent_claw_main/channels/telegram.py
Normal file
@@ -0,0 +1,71 @@
|
||||
import os
|
||||
import threading
|
||||
import urllib.request
|
||||
import json
|
||||
import boto3
|
||||
|
||||
|
||||
class TelegramAdapter:
|
||||
"""Channel adapter for Telegram Bot API."""
|
||||
|
||||
def __init__(self, chat_id: str, bot_token_secret_arn: str = ''):
|
||||
self.chat_id = str(chat_id)
|
||||
self._secret_arn = bot_token_secret_arn
|
||||
self._token: str | None = None
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def _get_token(self) -> str:
|
||||
if self._token is None:
|
||||
with self._lock:
|
||||
if self._token is None:
|
||||
secret_arn = self._secret_arn or os.environ.get(
|
||||
'TELEGRAM_BOT_TOKEN_SECRET_ARN',
|
||||
'arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/telegram-bot-token-Oq3in3'
|
||||
)
|
||||
sm = boto3.client('secretsmanager')
|
||||
self._token = sm.get_secret_value(
|
||||
SecretId=secret_arn
|
||||
)['SecretString']
|
||||
return self._token
|
||||
|
||||
def _api(self, method: str, data: dict) -> dict:
|
||||
token = self._get_token()
|
||||
body = json.dumps(data).encode()
|
||||
req = urllib.request.Request(
|
||||
f'https://api.telegram.org/bot{token}/{method}',
|
||||
data=body,
|
||||
headers={'Content-Type': 'application/json'},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as resp:
|
||||
return json.loads(resp.read())
|
||||
|
||||
def send(self, text: str) -> str:
|
||||
"""Send message, return message_id."""
|
||||
resp = self._api('sendMessage', {
|
||||
'chat_id': self.chat_id,
|
||||
'text': text,
|
||||
'parse_mode': 'Markdown',
|
||||
})
|
||||
return str(resp.get('result', {}).get('message_id', ''))
|
||||
|
||||
def send_typing(self) -> None:
|
||||
"""Send typing action (best-effort)."""
|
||||
try:
|
||||
self._api('sendChatAction', {
|
||||
'chat_id': self.chat_id,
|
||||
'action': 'typing',
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def edit(self, message_id: str, text: str) -> None:
|
||||
"""Edit an existing message in-place."""
|
||||
try:
|
||||
self._api('editMessageText', {
|
||||
'chat_id': self.chat_id,
|
||||
'message_id': int(message_id),
|
||||
'text': text,
|
||||
'parse_mode': 'Markdown',
|
||||
})
|
||||
except Exception:
|
||||
pass
|
||||
201
agentclaw/app/agent_claw_main/main.py
Normal file
201
agentclaw/app/agent_claw_main/main.py
Normal file
@@ -0,0 +1,201 @@
|
||||
"""
|
||||
agent-claw Runtime 1 — main assistant agent.
|
||||
|
||||
Entrypoint for AgentCore CodeZip deployment.
|
||||
"""
|
||||
import os
|
||||
from strands import Agent, tool
|
||||
from strands.models import BedrockModel
|
||||
from bedrock_agentcore.runtime import BedrockAgentCoreApp
|
||||
|
||||
from channels.telegram import TelegramAdapter
|
||||
from prompt_builder import build_system_prompt, invalidate_prompt
|
||||
from tools import web as web_tools
|
||||
from tools import workspace as ws_tools
|
||||
from tools import messaging
|
||||
from tools.home_assistant import home_assistant
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from strands.tools.mcp.mcp_client import MCPClient
|
||||
import httpx
|
||||
import botocore.auth
|
||||
import botocore.awsrequest
|
||||
import boto3
|
||||
from urllib.parse import urlparse as _urlparse
|
||||
|
||||
WORKSPACE_MCP_URL = 'https://25hugrzw4uwtueeg77jsmft6lq0wunmd.lambda-url.us-east-1.on.aws/mcp'
|
||||
|
||||
|
||||
class _SigV4HttpxAuth(httpx.Auth):
|
||||
"""SigV4 auth for Lambda Function URL with AWS_IAM."""
|
||||
def __init__(self, region: str = 'us-east-1'):
|
||||
self._region = region
|
||||
|
||||
def auth_flow(self, request):
|
||||
creds = boto3.Session().get_credentials().get_frozen_credentials()
|
||||
parsed = _urlparse(str(request.url))
|
||||
aws_req = botocore.awsrequest.AWSRequest(
|
||||
method=request.method,
|
||||
url=str(request.url),
|
||||
data=request.content or b'',
|
||||
headers={
|
||||
'Host': parsed.hostname,
|
||||
'Content-Type': request.headers.get('content-type', 'application/json'),
|
||||
'Accept': request.headers.get('accept', 'application/json, text/event-stream'),
|
||||
}
|
||||
)
|
||||
botocore.auth.SigV4Auth(creds, 'lambda', self._region).add_auth(aws_req)
|
||||
for k, v in aws_req.headers.items():
|
||||
request.headers[k] = v
|
||||
yield request
|
||||
from bedrock_agentcore.memory.integrations.strands.config import AgentCoreMemoryConfig
|
||||
from bedrock_agentcore.memory.integrations.strands.session_manager import AgentCoreMemorySessionManager
|
||||
from strands_tools.code_interpreter import AgentCoreCodeInterpreter as _CodeInterpreterClient
|
||||
|
||||
# Initialise once per warm session
|
||||
_code_interpreter = _CodeInterpreterClient(region='us-east-1')
|
||||
|
||||
app = BedrockAgentCoreApp()
|
||||
|
||||
|
||||
# ── Tool definitions ──────────────────────────────────────────────────────
|
||||
|
||||
@tool
|
||||
def send_message(text: str) -> str:
|
||||
"""Send a message to the user through their channel (Telegram, Slack, etc.)"""
|
||||
return messaging.send(text)
|
||||
|
||||
|
||||
@tool
|
||||
def web_search(query: str) -> str:
|
||||
"""Search the web using Brave Search. Returns titles, URLs, and snippets."""
|
||||
return web_tools.brave_search(query)
|
||||
|
||||
|
||||
@tool
|
||||
def web_fetch(url: str) -> str:
|
||||
"""Fetch and extract readable text content from a URL."""
|
||||
return web_tools.web_fetch(url)
|
||||
|
||||
|
||||
@tool
|
||||
def read_workspace_file(path: str) -> str:
|
||||
"""Read a file from the agent workspace (SOUL.md, HEARTBEAT.md, etc.)"""
|
||||
return ws_tools.read_file(path)
|
||||
|
||||
|
||||
@tool
|
||||
def write_workspace_file(path: str, content: str) -> str:
|
||||
"""Write or update a file in the agent workspace."""
|
||||
result = ws_tools.write_file(path, content)
|
||||
invalidate_prompt() # force system prompt rebuild if persona files changed
|
||||
return result
|
||||
|
||||
|
||||
# ── Entrypoint ────────────────────────────────────────────────────────────
|
||||
|
||||
@app.entrypoint
|
||||
def main(payload: dict, context) -> dict:
|
||||
"""Handle an invocation from agent-runner Lambda."""
|
||||
|
||||
# Set up channel adapter
|
||||
adapter_config = payload.get('channel_adapter', {})
|
||||
channel_type = adapter_config.get('type', 'telegram')
|
||||
|
||||
if channel_type == 'telegram':
|
||||
adapter = TelegramAdapter(
|
||||
chat_id=adapter_config.get('target_id', ''),
|
||||
bot_token_secret_arn=adapter_config.get('bot_token_secret_arn', ''),
|
||||
)
|
||||
else:
|
||||
# Future channels: instantiate appropriate adapter
|
||||
raise ValueError(f"Unsupported channel type: {channel_type}")
|
||||
|
||||
messaging.set_adapter(adapter)
|
||||
|
||||
# Start typing indicator immediately, keep it alive in background
|
||||
import threading
|
||||
_typing_active = True
|
||||
def _keep_typing():
|
||||
adapter.send_typing()
|
||||
import time
|
||||
while _typing_active:
|
||||
time.sleep(4)
|
||||
if _typing_active:
|
||||
adapter.send_typing()
|
||||
typing_thread = threading.Thread(target=_keep_typing, daemon=True)
|
||||
typing_thread.start()
|
||||
|
||||
# Set up AgentCore Memory session manager (short + long term via session_manager)
|
||||
MEMORY_ID = 'agentclaw_AgentClawMemory-i7Csf776AH'
|
||||
actor_id = payload.get('actor_id', adapter_config.get('target_id', 'default'))
|
||||
session_id = payload.get('session_id', f'session-{actor_id}')
|
||||
|
||||
memory_config = AgentCoreMemoryConfig(
|
||||
memory_id=MEMORY_ID,
|
||||
session_id=session_id,
|
||||
actor_id=actor_id,
|
||||
)
|
||||
session_manager = AgentCoreMemorySessionManager(
|
||||
agentcore_memory_config=memory_config,
|
||||
region_name='us-east-1',
|
||||
)
|
||||
|
||||
# Build system prompt (cached across warm invocations)
|
||||
system_prompt = build_system_prompt()
|
||||
|
||||
# Model: claude-sonnet-4-6 via cross-region inference
|
||||
model = BedrockModel(
|
||||
model_id="us.anthropic.claude-sonnet-4-6",
|
||||
region_name="us-east-1",
|
||||
)
|
||||
|
||||
base_tools = [send_message, web_search, web_fetch, read_workspace_file, write_workspace_file,
|
||||
_code_interpreter.code_interpreter, home_assistant]
|
||||
|
||||
def _run_agent(tools):
|
||||
agent = Agent(
|
||||
model=model,
|
||||
system_prompt=system_prompt,
|
||||
session_manager=session_manager,
|
||||
tools=tools,
|
||||
)
|
||||
return agent(payload.get('prompt', ''))
|
||||
|
||||
workspace_mcp_client = MCPClient(
|
||||
lambda: streamablehttp_client(WORKSPACE_MCP_URL, timeout=20, auth=_SigV4HttpxAuth())
|
||||
)
|
||||
workspace_tools = []
|
||||
try:
|
||||
with workspace_mcp_client:
|
||||
workspace_tools = workspace_mcp_client.list_tools_sync()
|
||||
except Exception as e:
|
||||
print(f'[main] workspace_mcp unavailable ({type(e).__name__}) — continuing without it')
|
||||
|
||||
try:
|
||||
result = _run_agent(base_tools + list(workspace_tools))
|
||||
finally:
|
||||
_typing_active = False
|
||||
|
||||
# Flush buffered memory events
|
||||
session_manager.close()
|
||||
|
||||
# Deliver final response — only send if agent didn't already call send_message tool.
|
||||
# If the tool was used, the response is already delivered. The fallback handles
|
||||
# cases where the agent responds directly without calling the tool.
|
||||
if not messaging.was_sent() and result.message:
|
||||
# Extract plain text from Strands result (avoid sending raw dict/JSON)
|
||||
msg = result.message
|
||||
if isinstance(msg, dict):
|
||||
content = msg.get('content', {})
|
||||
if isinstance(content, dict):
|
||||
msg = content.get('text', str(content))
|
||||
elif isinstance(content, list):
|
||||
msg = ' '.join(c.get('text', '') for c in content if isinstance(c, dict))
|
||||
else:
|
||||
msg = str(content)
|
||||
adapter.send(str(msg))
|
||||
|
||||
return {'result': result.message}
|
||||
|
||||
|
||||
app.run()
|
||||
1
agentclaw/app/agent_claw_main/mcp_client/__init__.py
Normal file
1
agentclaw/app/agent_claw_main/mcp_client/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Package marker
|
||||
14
agentclaw/app/agent_claw_main/mcp_client/client.py
Normal file
14
agentclaw/app/agent_claw_main/mcp_client/client.py
Normal file
@@ -0,0 +1,14 @@
|
||||
import os
|
||||
import logging
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from strands.tools.mcp.mcp_client import MCPClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# ExaAI provides information about code through web searches, crawling and code context searches through their platform. Requires no authentication
|
||||
EXAMPLE_MCP_ENDPOINT = "https://mcp.exa.ai/mcp"
|
||||
|
||||
def get_streamable_http_mcp_client() -> MCPClient:
|
||||
"""Returns an MCP Client compatible with Strands"""
|
||||
# to use an MCP server that supports bearer authentication, add headers={"Authorization": f"Bearer {access_token}"}
|
||||
return MCPClient(lambda: streamablehttp_client(EXAMPLE_MCP_ENDPOINT))
|
||||
1
agentclaw/app/agent_claw_main/model/__init__.py
Normal file
1
agentclaw/app/agent_claw_main/model/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# Package marker
|
||||
6
agentclaw/app/agent_claw_main/model/load.py
Normal file
6
agentclaw/app/agent_claw_main/model/load.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from strands.models.bedrock import BedrockModel
|
||||
|
||||
|
||||
def load_model() -> BedrockModel:
|
||||
"""Get Bedrock model client using IAM credentials."""
|
||||
return BedrockModel(model_id="global.anthropic.claude-sonnet-4-5-20250929-v1:0")
|
||||
44
agentclaw/app/agent_claw_main/prompt_builder.py
Normal file
44
agentclaw/app/agent_claw_main/prompt_builder.py
Normal file
@@ -0,0 +1,44 @@
|
||||
import os
|
||||
import boto3
|
||||
|
||||
# Cache: built once per warm session
|
||||
_system_prompt: str | None = None
|
||||
|
||||
|
||||
def build_system_prompt() -> str:
|
||||
"""Build system prompt from S3 workspace files (cached for warm session)."""
|
||||
global _system_prompt
|
||||
if _system_prompt is not None:
|
||||
return _system_prompt
|
||||
|
||||
bucket = os.environ.get('WORKSPACE_BUCKET_NAME', '') or 'agent-claw-workspace-495395224548'
|
||||
print(f'[prompt_builder] Loading from bucket: {bucket!r}')
|
||||
|
||||
if not bucket:
|
||||
print('[prompt_builder] WARNING: WORKSPACE_BUCKET_NAME not set!')
|
||||
_system_prompt = 'You are a helpful personal assistant.'
|
||||
return _system_prompt
|
||||
|
||||
s3 = boto3.client('s3')
|
||||
parts = []
|
||||
|
||||
for fname in ['SOUL.md', 'AGENTS.md', 'IDENTITY.md', 'USER.md', 'MEMORY.md', 'TOOLS.md']:
|
||||
try:
|
||||
obj = s3.get_object(Bucket=bucket, Key=fname)
|
||||
content = obj['Body'].read().decode('utf-8')
|
||||
parts.append(f'## {fname}\n{content}')
|
||||
print(f'[prompt_builder] Loaded {fname} ({len(content)} bytes)')
|
||||
except Exception as e:
|
||||
print(f'[prompt_builder] Failed to load {fname}: {e}')
|
||||
|
||||
parts.append('## Runtime\nRuntime: agent-claw | host=AgentCore | model=bedrock-claude-sonnet | channel=telegram\nCurrent date/time is provided by the system. Timezone: America/Chicago.')
|
||||
|
||||
_system_prompt = '\n\n---\n\n'.join(parts)
|
||||
print(f'[prompt_builder] System prompt built: {len(_system_prompt)} chars')
|
||||
return _system_prompt
|
||||
|
||||
|
||||
def invalidate_prompt() -> None:
|
||||
"""Force rebuild of system prompt on next invocation (call after workspace write)."""
|
||||
global _system_prompt
|
||||
_system_prompt = None
|
||||
21
agentclaw/app/agent_claw_main/pyproject.toml
Normal file
21
agentclaw/app/agent_claw_main/pyproject.toml
Normal file
@@ -0,0 +1,21 @@
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "agent_claw_main"
|
||||
version = "0.1.0"
|
||||
description = "AgentCore Runtime Application using Strands SDK"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = [
|
||||
"aws-opentelemetry-distro",
|
||||
"bedrock-agentcore >= 1.0.3",
|
||||
"botocore[crt] >= 1.35.0",
|
||||
"strands-agents-tools >= 0.5.0",
|
||||
"strands-agents >= 1.13.0",
|
||||
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
packages = ["."]
|
||||
5
agentclaw/app/agent_claw_main/tools/__init__.py
Normal file
5
agentclaw/app/agent_claw_main/tools/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .web import brave_search, web_fetch
|
||||
from .workspace import read_file, write_file
|
||||
from .messaging import send, set_adapter
|
||||
|
||||
__all__ = ['brave_search', 'web_fetch', 'read_file', 'write_file', 'send', 'set_adapter']
|
||||
68
agentclaw/app/agent_claw_main/tools/code_interpreter.py
Normal file
68
agentclaw/app/agent_claw_main/tools/code_interpreter.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""Code interpreter tool — runs Python code in AgentCore managed sandbox."""
|
||||
import os
|
||||
import base64
|
||||
from strands import tool
|
||||
|
||||
|
||||
def _parse_stream(result: dict) -> str:
|
||||
"""Parse the streaming response from invoke_code_interpreter."""
|
||||
parts = []
|
||||
if "stream" not in result:
|
||||
return str(result)
|
||||
|
||||
for event in result["stream"]:
|
||||
if "result" not in event:
|
||||
continue
|
||||
for item in event["result"].get("content", []):
|
||||
item_type = item.get("type", "")
|
||||
if item_type == "text":
|
||||
text = item.get("text", "")
|
||||
if text:
|
||||
parts.append(text)
|
||||
elif item_type == "resource":
|
||||
resource = item.get("resource", {})
|
||||
if "text" in resource:
|
||||
parts.append(resource["text"])
|
||||
elif "blob" in resource:
|
||||
try:
|
||||
parts.append(base64.b64decode(resource["blob"]).decode("utf-8"))
|
||||
except Exception:
|
||||
parts.append(f"<binary resource: {resource.get('uri', '?')}>")
|
||||
elif item_type == "image":
|
||||
# Base64-encoded image
|
||||
image_data = item.get("source", {}).get("data", "")
|
||||
mime = item.get("source", {}).get("mediaType", "image/png")
|
||||
parts.append(f"<image: {mime}, {len(image_data)} bytes base64>")
|
||||
|
||||
return "\n".join(parts) if parts else "(no output)"
|
||||
|
||||
|
||||
@tool
|
||||
def run_code(code: str, packages: list[str] | None = None) -> str:
|
||||
"""Execute Python code in a secure managed sandbox and return the output.
|
||||
Optionally install pip packages before running (e.g. ['pandas', 'numpy']).
|
||||
|
||||
Args:
|
||||
code: Python code to execute.
|
||||
packages: Optional list of pip packages to install first.
|
||||
|
||||
Returns:
|
||||
Execution output (stdout, results, errors).
|
||||
"""
|
||||
try:
|
||||
from bedrock_agentcore.tools import CodeInterpreter, code_session
|
||||
|
||||
region = os.environ.get('AWS_REGION', 'us-east-1')
|
||||
|
||||
with code_session(region) as client:
|
||||
if packages:
|
||||
install_raw = client.install_packages(packages)
|
||||
install_out = _parse_stream(install_raw) if isinstance(install_raw, dict) else str(install_raw)
|
||||
print(f'[code_interpreter] install: {install_out[:200]}')
|
||||
|
||||
raw = client.execute_code(code)
|
||||
return _parse_stream(raw)
|
||||
|
||||
except Exception as e:
|
||||
import traceback
|
||||
return f'Code interpreter error: {type(e).__name__}: {e}\n{traceback.format_exc()[-500:]}'
|
||||
91
agentclaw/app/agent_claw_main/tools/home_assistant.py
Normal file
91
agentclaw/app/agent_claw_main/tools/home_assistant.py
Normal file
@@ -0,0 +1,91 @@
|
||||
"""Home Assistant tool — control and query HA entities via REST API."""
|
||||
import json
|
||||
import os
|
||||
import urllib.request
|
||||
import urllib.error
|
||||
from strands import tool
|
||||
|
||||
HA_URL = "https://homeassistant.home.everyonce.com"
|
||||
# Token stored in workspace or env; fallback to hardcoded for AgentCore runtime
|
||||
HA_TOKEN = os.environ.get(
|
||||
"HA_TOKEN",
|
||||
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJlMDExN2YwNzhlM2Q0NjViODJhNjJiZWFiMzI1ZWU4MiIsImlhdCI6MTc3MTM1MjU0MiwiZXhwIjoyMDg2NzEyNTQyfQ.UySLD6JV4e_bdd1nQjdbZcimdCD6B3kBGDftcRz1H6Q"
|
||||
)
|
||||
|
||||
|
||||
def _ha_request(method: str, path: str, body: dict | None = None) -> dict | list:
|
||||
url = f"{HA_URL}{path}"
|
||||
headers = {
|
||||
"Authorization": f"Bearer {HA_TOKEN}",
|
||||
"Content-Type": "application/json",
|
||||
}
|
||||
data = json.dumps(body).encode() if body else None
|
||||
req = urllib.request.Request(url, data=data, headers=headers, method=method)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
return json.loads(resp.read().decode())
|
||||
except urllib.error.HTTPError as e:
|
||||
return {"error": f"HTTP {e.code}: {e.reason}", "body": e.read().decode()[:500]}
|
||||
except Exception as e:
|
||||
return {"error": str(e)}
|
||||
|
||||
|
||||
@tool
|
||||
def home_assistant(action: str, entity_id: str = "", domain: str = "", service: str = "",
|
||||
service_data: dict | None = None) -> str:
|
||||
"""Control and query your Home Assistant smart home.
|
||||
|
||||
Actions:
|
||||
- "get_state": Get the current state of a specific entity (requires entity_id).
|
||||
- "list_states": List all entity states (optionally filter by domain prefix like 'light', 'switch', 'climate', 'sensor').
|
||||
- "call_service": Call a HA service (requires domain, service, and optional service_data with entity_id).
|
||||
- "get_history": Not yet implemented.
|
||||
|
||||
Common service examples:
|
||||
- Turn light on: domain="light", service="turn_on", service_data={"entity_id": "light.living_room"}
|
||||
- Turn light off: domain="light", service="turn_off", service_data={"entity_id": "light.living_room"}
|
||||
- Set brightness: domain="light", service="turn_on", service_data={"entity_id": "light.x", "brightness_pct": 50}
|
||||
- Lock door: domain="lock", service="lock", service_data={"entity_id": "lock.front_door"}
|
||||
- Set thermostat: domain="climate", service="set_temperature", service_data={"entity_id": "climate.x", "temperature": 72}
|
||||
|
||||
Args:
|
||||
action: One of "get_state", "list_states", "call_service".
|
||||
entity_id: Entity ID for get_state (e.g. "light.living_room").
|
||||
domain: Service domain for call_service (e.g. "light", "switch", "lock", "climate").
|
||||
service: Service name for call_service (e.g. "turn_on", "turn_off", "lock").
|
||||
service_data: Dict of extra params for call_service (e.g. {"entity_id": "light.x", "brightness_pct": 80}).
|
||||
|
||||
Returns:
|
||||
JSON string with the result.
|
||||
"""
|
||||
if action == "get_state":
|
||||
if not entity_id:
|
||||
return "entity_id is required for get_state"
|
||||
result = _ha_request("GET", f"/api/states/{entity_id}")
|
||||
if isinstance(result, dict) and "error" not in result:
|
||||
return f"{entity_id}: {result.get('state')} (attrs: {json.dumps(result.get('attributes', {}))[:300]})"
|
||||
return json.dumps(result)
|
||||
|
||||
elif action == "list_states":
|
||||
result = _ha_request("GET", "/api/states")
|
||||
if isinstance(result, list):
|
||||
# Filter by domain prefix if entity_id used as filter
|
||||
prefix = entity_id or domain
|
||||
if prefix:
|
||||
result = [s for s in result if s.get("entity_id", "").startswith(prefix)]
|
||||
# Return concise summary
|
||||
lines = [f"{s['entity_id']}: {s['state']}" for s in result[:50]]
|
||||
return "\n".join(lines) + (f"\n... ({len(result)} total)" if len(result) > 50 else "")
|
||||
return json.dumps(result)
|
||||
|
||||
elif action == "call_service":
|
||||
if not domain or not service:
|
||||
return "domain and service are required for call_service"
|
||||
body = service_data or {}
|
||||
if entity_id and "entity_id" not in body:
|
||||
body["entity_id"] = entity_id
|
||||
result = _ha_request("POST", f"/api/services/{domain}/{service}", body)
|
||||
return f"Service {domain}.{service} called successfully" if isinstance(result, list) else json.dumps(result)
|
||||
|
||||
else:
|
||||
return f"Unknown action: {action}. Use 'get_state', 'list_states', or 'call_service'."
|
||||
30
agentclaw/app/agent_claw_main/tools/messaging.py
Normal file
30
agentclaw/app/agent_claw_main/tools/messaging.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Messaging tool — channel-adapter-backed send_message for the agent."""
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from channels.adapter import ChannelAdapter
|
||||
|
||||
# Injected by main.py before each invocation
|
||||
_adapter: 'ChannelAdapter | None' = None
|
||||
_message_sent: bool = False
|
||||
|
||||
|
||||
def set_adapter(adapter: 'ChannelAdapter') -> None:
|
||||
global _adapter, _message_sent
|
||||
_adapter = adapter
|
||||
_message_sent = False # reset on each new invocation
|
||||
|
||||
|
||||
def was_sent() -> bool:
|
||||
"""Returns True if send() was called during this invocation."""
|
||||
return _message_sent
|
||||
|
||||
|
||||
def send(text: str) -> str:
|
||||
"""Send a message to the user via the active channel adapter."""
|
||||
global _message_sent
|
||||
if _adapter is None:
|
||||
return 'No channel adapter configured.'
|
||||
msg_id = _adapter.send(text)
|
||||
_message_sent = True
|
||||
return f"Sent (id={msg_id})" if msg_id else 'Sent'
|
||||
68
agentclaw/app/agent_claw_main/tools/web.py
Normal file
68
agentclaw/app/agent_claw_main/tools/web.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import os
|
||||
import threading
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import json
|
||||
import boto3
|
||||
|
||||
# Brave Search API
|
||||
_brave_key: str | None = None
|
||||
_brave_lock = threading.Lock()
|
||||
|
||||
|
||||
def _get_brave_key() -> str:
|
||||
global _brave_key
|
||||
if _brave_key is None:
|
||||
with _brave_lock:
|
||||
if _brave_key is None:
|
||||
secret_arn = os.environ.get(
|
||||
'BRAVE_API_KEY_SECRET_ARN',
|
||||
'arn:aws:secretsmanager:us-east-1:495395224548:secret:agent-claw/brave-api-key-uUSgzi'
|
||||
)
|
||||
sm = boto3.client('secretsmanager')
|
||||
_brave_key = sm.get_secret_value(SecretId=secret_arn)['SecretString']
|
||||
return _brave_key
|
||||
|
||||
|
||||
def brave_search(query: str, count: int = 5) -> str:
|
||||
"""Search the web using Brave Search API."""
|
||||
api_key = _get_brave_key()
|
||||
params = urllib.parse.urlencode({'q': query, 'count': count})
|
||||
req = urllib.request.Request(
|
||||
f'https://api.search.brave.com/res/v1/web/search?{params}',
|
||||
headers={
|
||||
'Accept': 'application/json',
|
||||
'X-Subscription-Token': api_key,
|
||||
},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=10) as resp:
|
||||
data = json.loads(resp.read())
|
||||
|
||||
results = data.get('web', {}).get('results', [])
|
||||
if not results:
|
||||
return 'No results found.'
|
||||
|
||||
parts = []
|
||||
for r in results:
|
||||
parts.append(f"**{r.get('title', '')}**\n{r.get('url', '')}\n{r.get('description', '')}")
|
||||
return '\n\n'.join(parts)
|
||||
|
||||
|
||||
def web_fetch(url: str) -> str:
|
||||
"""Fetch and return text content from a URL."""
|
||||
req = urllib.request.Request(
|
||||
url,
|
||||
headers={'User-Agent': 'Mozilla/5.0 (compatible; agent-claw/1.0)'},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||
raw = resp.read(1024 * 1024) # cap at 1MB
|
||||
|
||||
# Basic text extraction (strip HTML tags)
|
||||
import re
|
||||
text = raw.decode('utf-8', errors='ignore')
|
||||
text = re.sub(r'<script[^>]*>.*?</script>', '', text, flags=re.DOTALL | re.IGNORECASE)
|
||||
text = re.sub(r'<style[^>]*>.*?</style>', '', text, flags=re.DOTALL | re.IGNORECASE)
|
||||
text = re.sub(r'<[^>]+>', ' ', text)
|
||||
text = re.sub(r'[ \t]+', ' ', text)
|
||||
text = re.sub(r'\n{3,}', '\n\n', text)
|
||||
return text[:8000].strip()
|
||||
48
agentclaw/app/agent_claw_main/tools/workspace.py
Normal file
48
agentclaw/app/agent_claw_main/tools/workspace.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
import boto3
|
||||
|
||||
# In-memory cache for workspace files (lives for the duration of the warm session)
|
||||
_cache: dict[str, str] = {}
|
||||
_s3 = None
|
||||
|
||||
|
||||
def _get_s3():
|
||||
global _s3
|
||||
if _s3 is None:
|
||||
_s3 = boto3.client('s3')
|
||||
return _s3
|
||||
|
||||
|
||||
def get_bucket() -> str:
|
||||
return os.environ['WORKSPACE_BUCKET_NAME']
|
||||
|
||||
|
||||
def read_file(path: str) -> str:
|
||||
"""Read a workspace file from S3 (cached)."""
|
||||
if path not in _cache:
|
||||
resp = _get_s3().get_object(Bucket=get_bucket(), Key=path)
|
||||
_cache[path] = resp['Body'].read().decode('utf-8')
|
||||
return _cache[path]
|
||||
|
||||
|
||||
def write_file(path: str, content: str) -> str:
|
||||
"""Write a workspace file to S3 and update cache."""
|
||||
_get_s3().put_object(
|
||||
Bucket=get_bucket(),
|
||||
Key=path,
|
||||
Body=content.encode('utf-8'),
|
||||
ContentType='text/markdown',
|
||||
)
|
||||
_cache[path] = content
|
||||
return f"Written {len(content)} bytes to {path}"
|
||||
|
||||
|
||||
def load_persona_files() -> dict[str, str]:
|
||||
"""Load all persona files at session start (SOUL.md etc.)"""
|
||||
files = {}
|
||||
for fname in ['SOUL.md', 'AGENTS.md', 'IDENTITY.md', 'USER.md']:
|
||||
try:
|
||||
files[fname] = read_file(fname)
|
||||
except Exception:
|
||||
pass
|
||||
return files
|
||||
2441
agentclaw/app/agent_claw_main/uv.lock
generated
Normal file
2441
agentclaw/app/agent_claw_main/uv.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user