wip on package

This commit is contained in:
dexhorthy
2025-06-29 13:48:11 -05:00
parent 4901aa68cc
commit dbf60d38b4
12 changed files with 902 additions and 2 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
.promptx

23
12fa-a2h-plan Normal file
View File

@@ -0,0 +1,23 @@
## Goals
A working implementation of
npx create-12-factor-agent
that follows the basic template in packages/create-12-factor-agent/template
includes by default:
- typescript
- everything in the package should be usable as a standalone package and exported, to support:
- a npx @humanlayer/create command to create a new 12-factor agent with `humanlayer: true`
### python pacakge
A python repo template similar to the typescript template, but uses python and uv instead of typescript.
A working implementation of
`uvx create-12-factor-agent` that follows
all the above goals, but uses python to create the repo and uses a python repo template. all other flags and toggles are the same

91
CLAUDE.md Normal file
View File

@@ -0,0 +1,91 @@
# AI Assistant Instructions
**IMPORTANT: Copy or merge this file into your project's CLAUDE.md file to activate agent personas.**
## 🚨 MANDATORY PERSONA SELECTION
**CRITICAL: You MUST adopt one of the specialized personas before proceeding with any work.**
**BEFORE DOING ANYTHING ELSE**, you must read and adopt one of these personas:
1. **Developer Agent** - Read `.promptx/personas/agent-developer.md` - For coding, debugging, and implementation tasks
2. **Code Reviewer Agent** - Read `.promptx/personas/agent-code-reviewer.md` - For reviewing code changes and quality assurance
3. **Rebaser Agent** - Read `.promptx/personas/agent-rebaser.md` - For cleaning git history and rebasing changes
4. **Merger Agent** - Read `.promptx/personas/agent-merger.md` - For merging code across branches
5. **Multiplan Manager Agent** - Read `.promptx/personas/agent-multiplan-manager.md` - For orchestrating parallel work and creating plans
**DO NOT PROCEED WITHOUT SELECTING A PERSONA.** Each persona has specific rules, workflows, and tools that you MUST follow exactly.
## How to Choose Your Persona
- **Asked to write code, fix bugs, or implement features?** → Use Developer Agent
- **Asked to review code changes?** → Use Code Reviewer Agent
- **Asked to clean git history or rebase changes?** → Use Rebaser Agent
- **Asked to merge branches or consolidate work?** → Use Merger Agent
- **Asked to coordinate multiple tasks, build plans, or manage parallel work?** → Use Multiplan Manager Agent
## Project Context
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
This project uses:
- **Language/Framework**: [Add your stack here]
- **Build Tool**: [Add your build commands]
- **Testing**: [Add your test commands]
- **Architecture**: [Describe your project structure]
## Core Principles (All Personas)
1. **READ FIRST**: Always read at least 1500 lines to understand context fully
2. **DELETE MORE THAN YOU ADD**: Complexity compounds into disasters
3. **FOLLOW EXISTING PATTERNS**: Don't invent new approaches
4. **BUILD AND TEST**: Run your build and test commands after changes
5. **COMMIT FREQUENTLY**: Every 5-10 minutes for meaningful progress
## File Structure Reference
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
```
./
├── package.json # [or your dependency file]
├── src/ # [your source directory]
│ ├── [your modules]
│ └── [your files]
├── test/ # [your test directory]
├── .promptx/ # Agent personas (created by promptx init)
│ └── personas/
└── CLAUDE.md # This file (after merging)
```
## Common Commands (All Personas)
[CUSTOMIZE THIS SECTION FOR YOUR PROJECT]
```bash
# Build project
[your build command]
# Run tests
[your test command]
# Lint code
[your lint command]
# Deploy locally
[your deploy command]
```
## CRITICAL REMINDER
**You CANNOT proceed without adopting a persona.** Each persona has:
- Specific workflows and rules
- Required tools and commands
- Success criteria and verification steps
- Commit and progress requirements
**Choose your persona now and follow its instructions exactly.**
---
*Generated by promptx - Agent personas are in .promptx/personas/*

12
Makefile Normal file
View File

@@ -0,0 +1,12 @@
# Makefile for launch compatibility
.PHONY: setup teardown
setup:
@echo "Setting up project..."
@npm install || bun install || yarn install
@echo "Setup complete!"
teardown:
@echo "Tearing down project..."
@rm -rf node_modules
@echo "Teardown complete!"

368
drafts/a2h-spec.md Normal file
View File

@@ -0,0 +1,368 @@
# A2H - The Agent-to-Human Protocol
## Overview
A2H is a service that allows an agent to request human interaction
## Why another protocol?
MCP and A2A are not enough
## Shoulds
- Clients should respect A2H_BASE_URL and A2H_API_KEY environment variables if set, to allow for simple oauth2-based authentication to REST services.
## Core Protocol
### Scopes
The A2H protocol supports two scopes:
- The agent side, APIs consumed by an agent to request human interaction
- The (Optional) admin side, APIs consumed by an admin or web application to manage humans and their contact channels
This separation allows for agents to query and find humans to contact, without exposing the human's contact details to the agent. It is the responsibility of the A2H provider to relay agent requests to the appropriate human via that human's preferred contact channel(s).
### Objects
```
apiVersion: proto.a2h.dev/v1alpha1
kind: Message
metatdata:
uid: "123"
spec: # spec sent by agent
message: "" # message from the agent
response_schema:
# optional, json schema for the response,
channel_id:
status: # status resolved by a2h server
humanMessage: "" # message from the human
response:
# optional, matches spec schema
```
```
apiVersion: proto.a2h.dev/v1alpha1
kind: NewConversation
metadata:
uid: "abc"
spec: # spec sent by a2h server
message: "" # message from the agent
channel_id: "123" # channel id to use for future conversations
response_schema:
# optional, json schema for the response,
```
#### HumanContact
```json
{
"run_id": "run_123",
"call_id": "call_456",
"spec": {
"msg": "I've tried using the tool to refund the customer but its returning a 500 error. Can you help?",
"channel": {
"slack": {
"channel_or_user_id": "U1234567890",
"context_about_channel_or_user": "Support team lead"
}
},
},
}
```
A HumanContact represents a request for human interaction. It contains:
- `run_id` (string): Unique identifier for the run
- `call_id` (string): Unique identifier for the contact request
- `spec` (HumanContactSpec): The specification for the contact request
- `status` (HumanContactStatus, optional): The current status of the contact request
The HumanContactSpec contains:
- `msg` (string): The message to send to the human
- `subject` (string, optional): Subject of the contact request
- `channel` (ContactChannel, optional): The channel to use for contact
- `response_options` (ResponseOption[], optional): Available response options
- `state` (object, optional): Additional state information
The HumanContactStatus contains:
- `requested_at` (datetime, optional): When the contact was requested
- `responded_at` (datetime, optional): When the human responded
- `response` (string, optional): The human's response
- `response_option_name` (string, optional): Name of the selected response option
- `slack_message_ts` (string, optional): Slack message timestamp if applicable
- `failed_validation_details` (object, optional): Details if validation failed
#### FunctionCall
Example:
```json
{
"run_id": "run_789",
"call_id": "call_101",
"spec": {
"fn": "process_payment",
"kwargs": {
"amount": 100.00,
"currency": "USD",
"recipient": "merchant_123"
},
"channel": {
"email": {
"address": "ap@example.com",
}
},
},
"status": {
"requested_at": "2024-03-20T11:00:00Z",
"responded_at": "2024-03-20T11:02:00Z",
"approved": true,
"comment": "Payment looks good, approved",
"user_info": {
"name": "John Doe",
"role": "Finance Manager"
},
"slack_message_ts": "1234567890.123457"
}
}
```
A FunctionCall represents a request for human approval of a function execution. It contains:
- `run_id` (string): Unique identifier for the run
- `call_id` (string): Unique identifier for the function call
- `spec` (FunctionCallSpec): The specification for the function call
- `status` (FunctionCallStatus, optional): The current status of the function call
The FunctionCallSpec contains:
- `fn` (string): The function to be called
- `kwargs` (object): The keyword arguments for the function
- `channel` (ContactChannel, optional): The channel to use for contact
- `reject_options` (ResponseOption[], optional): Available rejection options
- `state` (object, optional): Additional state information
The FunctionCallStatus contains:
- `requested_at` (datetime, optional): When the approval was requested
- `responded_at` (datetime, optional): When the human responded
- `approved` (boolean, optional): Whether the function call was approved
- `comment` (string, optional): Any comment from the human
- `user_info` (object, optional): Information about the responding user
- `slack_context` (object, optional): Slack-specific context
- `reject_option_name` (string, optional): Name of the selected rejection option
- `slack_message_ts` (string, optional): Slack message timestamp if applicable
- `failed_validation_details` (object, optional): Details if validation failed
#### ContactChannel
Example:
```json
{
"slack": {
"channel_or_user_id": "U1234567890",
"context_about_channel_or_user": "Support team lead",
"allowed_responder_ids": ["U1234567890", "U2345678901"],
"experimental_slack_blocks": true,
"thread_ts": "1234567890.123456"
}
}
```
or
```json
{
"email": {
"address": "ap@example.com",
"context_about_user": "Accounts Payable",
"in_reply_to_message_id": "1234567890",
"references_message_id": "1234567890",
"template": "<html><body>...</body></html>"
}
}
```
A ContactChannel represents a channel through which a human can be contacted. The protocol supports several channel types:
1. SlackContactChannel:
- `channel_or_user_id` (string): The Slack channel or user ID
- `context_about_channel_or_user` (string, optional): Additional context
- `bot_token` (string, optional): Bot token for authentication
- `allowed_responder_ids` (string[], optional): IDs of allowed responders
- `experimental_slack_blocks` (boolean, optional): Enable experimental blocks
- `thread_ts` (string, optional): Thread timestamp for threaded messages
2. SMSContactChannel:
- `phone_number` (string): The phone number to contact
- `context_about_user` (string, optional): Additional context about the user
3. WhatsAppContactChannel:
- `phone_number` (string): The phone number to contact
- `context_about_user` (string, optional): Additional context about the user
#### Human (Agent Side)
From the agent's perspective, a human is an object that has a name and description.
#### Human (Admin Side)
From the admin's perspective, a human is an object that has a name, description, and a list of prioritized contact channels, with details
### Agent Endpoints
#### POST /human_contacts
#### GET /human_contacts/:call_id
#### POST /function_calls
#### GET /function_calls/:call_id
## Extended Protocol
- Admin Humans
- Agent Humans Get
- Agent Humans Search
- Agent Channels List
- Agent Channels validate
### Objects
#### Human (Agent Side)
From the agent's perspective, a human is an object that has a name and description.
#### Human (Admin Side)
From the admin's perspective, a human is an object that has a name, description, and a list of prioritized contact channels, with details
### Agent Endpoints
#### GET /channels
return what contact channels are available and their supported fields
example response:
```json
{
"channels": {
"slack": {
"channelOrUserId": {
"type": "string",
"description": "The Slack channel or user ID to send messages to"
},
"contextAboutChannelOrUser": {
"type": "string",
"description": "Additional context about the Slack channel or user"
}
},
"email": {
"address": {
"type": "string",
"description": "Email address to send messages to"
},
"contextAboutUser": {
"type": "string",
"description": "Additional context about the email recipient"
},
"inReplyToMessageId": {
"type": "string",
"description": "The message ID of the email to reply to"
},
"referencesMessageId": {
"type": "string",
"description": "The message ID of the email to reference"
}
}
}
}
```
#### GET /humans
return a list of humans that are available to interact with
example response:
```json
{
"humans": [
{
"id": "654",
"name": "Jane Doe",
"description": "Jane Doe is a human who knows about technology and entrepreneurship",
},
{
"id": "123",
"name": "John Doe",
"description": "John Doe is a human who knows about sales and marketing"
}
]
}
#### GET /humans/search?q=
search for humans by name or description
example response:
```json
{
"humans": [
{
"id": "654",
"name": "Jane Doe",
"description": "Jane Doe is a human who knows about technology and entrepreneurship",
},
]
}
```
### Administrative Endpoints
#### POST /humans
Enroll a new human for agent contact
example request:
```json
{
"name": "John Doe",
"description": "John Doe is a human who knows about sales and marketing",
"prioritizedContactChannels": [
{
"slack": {
"channelOrUserId": "U1234567890",
}
},
{
"email": {
"address": "john.doe@example.com",
}
}
]
}
```
#### GET /humans/:id
Get a human by id
example response:
```json

0
drafts/ah2-openapi.json Normal file
View File

47
find_a2h_spec.sh Executable file
View File

@@ -0,0 +1,47 @@
#!/bin/bash
# Script to find A2H spec in git history across all repos
echo "=== Searching for A2H spec across all repositories ==="
echo
REPOS=(
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
)
for repo in "${REPOS[@]}"; do
if [ -d "$repo/.git" ]; then
echo "🔍 Searching in: $repo"
cd "$repo"
echo " 📋 All branches:"
git branch -a | grep -E "(a2h|agent.*human)" || true
echo " 🔍 Branches containing A2H in name:"
git branch -a | grep -i a2h || true
echo " 📝 Commits mentioning A2H:"
git log --all --oneline --grep="humanMessage" -i || true
echo " 📁 Files in git history containing A2H:"
git log --all --name-only --pretty=format: | grep -i "a2h\|agent.*human" | sort -u || true
echo " 🔍 Search file contents in all branches:"
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///'); do
echo " Checking branch: $branch"
git show "$branch" 2>/dev/null | grep -l -i "a2h\|agent.*human\|agent-to-human\|humanMessage" 2>/dev/null || true
done
echo " 📋 Recent commits (last 50):"
git log --all --oneline -50 | grep -i "spec\|a2h\|agent.*human\|humanMessage" || true
echo
else
echo "❌ Not a git repo: $repo"
fi
done
echo "=== Done searching ==="

69
find_k8s_a2h_focused.sh Executable file
View File

@@ -0,0 +1,69 @@
#!/bin/bash
# Focused search for kubernetes-style A2H spec
echo "=== Focused search for Kubernetes-style A2H declarations ==="
echo
REPOS=(
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
)
for repo in "${REPOS[@]}"; do
if [ -d "$repo/.git" ]; then
echo "🔍 Searching in: $repo"
cd "$repo"
# Search for apiVersion with a2h in any markdown files
echo " 📋 Files with apiVersion.*a2h:"
find . -name "*.md" -not -path "*/node_modules/*" -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
# Search for kind: followed by common A2H object names
echo " 📋 Files with kind: HumanContact|FunctionCall|Contact:"
find . -name "*.md" -not -path "*/node_modules/*" -exec grep -l "kind:.*\(HumanContact\|FunctionCall\|Contact\|Human\)" {} \; 2>/dev/null || true
# Search in all branches for files with these patterns
echo " 📋 Git history search for apiVersion.*a2h:"
git log --all --name-only --pretty=format: | grep "\.md$" | sort -u | head -20 | while read file; do
if [ -n "$file" ]; then
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///' | head -10); do
content=$(git show "$branch:$file" 2>/dev/null)
if echo "$content" | grep -q "apiVersion.*a2h"; then
echo " Found apiVersion.*a2h in $branch:$file"
break
fi
done
fi
done
echo
fi
done
echo "=== Manual content check of key files ==="
# Check specific files that might contain the spec
for repo in "${REPOS[@]}"; do
if [ -d "$repo" ]; then
echo "🔍 Checking key files in: $repo"
# Look for README, spec, docs files
find "$repo" -name "*.md" -not -path "*/node_modules/*" | grep -E "(spec|doc|readme|acep|crd|k8s|kubernetes)" | head -10 | while read file; do
if grep -q "kind:" "$file" 2>/dev/null; then
echo " 📝 Found kind: in: $file"
grep -n "kind:" "$file" 2>/dev/null | head -3
fi
if grep -q "apiVersion.*a2h" "$file" 2>/dev/null; then
echo " 📝 Found apiVersion.*a2h in: $file"
grep -n "apiVersion.*a2h" "$file" 2>/dev/null | head -3
fi
done
echo
fi
done
echo "=== Done focused search ==="

81
find_k8s_a2h_spec.sh Executable file
View File

@@ -0,0 +1,81 @@
#!/bin/bash
# Script to find kubernetes-style A2H spec with kind: declarations
echo "=== Searching for Kubernetes-style A2H spec across all repositories ==="
echo
REPOS=(
"/Users/dex/go/src/github.com/humanlayer/12-factor-agents"
"/Users/dex/go/src/github.com/humanlayer/humanlayer"
"/Users/dex/go/src/github.com/humanlayer/agentcontrolplane"
"/Users/dex/go/src/github.com/metalytics-dev/metalytics"
)
for repo in "${REPOS[@]}"; do
if [ -d "$repo/.git" ]; then
echo "🔍 Searching in: $repo"
cd "$repo"
echo " 📋 Files containing 'kind:' in current branch:"
find . -name "*.md" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
find . -name "*.yaml" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
find . -name "*.yml" -type f -exec grep -l "kind:" {} \; 2>/dev/null || true
echo " 📋 Files containing 'apiVersion.*a2h' in current branch:"
find . -name "*.md" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
find . -name "*.yaml" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
find . -name "*.yml" -type f -exec grep -l "apiVersion.*a2h" {} \; 2>/dev/null || true
echo " 🔍 Searching git history for kind: declarations:"
git log --all --name-only --pretty=format: | grep -E "\.(md|yaml|yml)$" | sort -u | while read file; do
if [ -n "$file" ]; then
# Check if file contains kind: in any branch
for branch in $(git branch -r | grep -v HEAD | sed 's/origin\///'); do
content=$(git show "$branch:$file" 2>/dev/null)
if [[ "$content" =~ kind:[[:space:]]*[A-Za-z] ]]; then
echo " Found kind: in $branch:$file"
echo "$content" | grep -n "kind:" || true
fi
if [[ "$content" =~ apiVersion.*a2h ]]; then
echo " Found apiVersion.*a2h in $branch:$file"
echo "$content" | grep -n "apiVersion.*a2h" || true
fi
done
fi
done
echo " 🔍 Commits mentioning kubernetes-style objects:"
git log --all --oneline --grep="kind:" --grep="apiVersion" --grep="HumanContact" --grep="FunctionCall" -i || true
echo " 📝 Search for specific A2H object types:"
find . -type f \( -name "*.md" -o -name "*.yaml" -o -name "*.yml" \) -exec grep -l -i "humancontact\|functioncall\|contactchannel" {} \; 2>/dev/null || true
echo
else
echo "❌ Not a git repo: $repo"
fi
done
echo "=== Detailed content search ==="
# More detailed search for yaml-like content in markdown files
for repo in "${REPOS[@]}"; do
if [ -d "$repo/.git" ]; then
echo "🔍 Detailed search in: $repo"
cd "$repo"
# Search for yaml blocks in markdown files that contain kind:
find . -name "*.md" -type f -exec sh -c '
file="$1"
# Look for yaml code blocks containing kind:
awk "/^```(yaml|yml)$/,/^```$/" "$file" | grep -q "kind:" && echo "YAML block with kind: found in $file"
# Look for any kind: declaration in markdown
grep -n "kind:" "$file" 2>/dev/null && echo "kind: found in $file"
' _ {} \; 2>/dev/null || true
echo
fi
done
echo "=== Done searching ==="

View File

@@ -8,10 +8,12 @@
"name": "my-agent",
"version": "0.1.0",
"dependencies": {
"@boundaryml/baml": "latest",
"express": "^5.1.0",
"humanlayer": "^0.7.7",
"tsx": "^4.15.0",
"typescript": "^5.0.0"
"typescript": "^5.0.0",
"zod": "^3.25.64"
},
"devDependencies": {
"@types/express": "^5.0.1",
@@ -22,6 +24,142 @@
"supertest": "^7.1.0"
}
},
"node_modules/@boundaryml/baml": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml/-/baml-0.90.0.tgz",
"integrity": "sha512-KNhP2XL4Kl7K8i5tQLr0L87mcGYDB2AjLwHwZpNgFineqsRCU66G+zlJuDY5KeX6xvJ11i3glz8IJrfIEzz5uw==",
"license": "MIT",
"dependencies": {
"@scarf/scarf": "^1.3.0"
},
"bin": {
"baml-cli": "cli.js"
},
"engines": {
"node": ">= 10"
},
"optionalDependencies": {
"@boundaryml/baml-darwin-arm64": "0.90.0",
"@boundaryml/baml-darwin-x64": "0.90.0",
"@boundaryml/baml-linux-arm64-gnu": "0.90.0",
"@boundaryml/baml-linux-arm64-musl": "0.90.0",
"@boundaryml/baml-linux-x64-gnu": "0.90.0",
"@boundaryml/baml-linux-x64-musl": "0.90.0",
"@boundaryml/baml-win32-x64-msvc": "0.90.0"
}
},
"node_modules/@boundaryml/baml-darwin-arm64": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-darwin-arm64/-/baml-darwin-arm64-0.90.0.tgz",
"integrity": "sha512-cWangAtYyow9scLrwLz5zFKgG52BF2XqQzZV//YLZEEcjuSNdpewXiPSenaOrE0bF9SuyLr3pwYeeGclG0gtLQ==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-darwin-x64": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-darwin-x64/-/baml-darwin-x64-0.90.0.tgz",
"integrity": "sha512-EzsaEaS5Hgxd7A7bHzIhfPP4efCRhCm2vmO11UGQDsT73fkDZ99NDbgItyAmVaePAkStjkrSKvT6sy0iqWA2XQ==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-linux-arm64-gnu": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-arm64-gnu/-/baml-linux-arm64-gnu-0.90.0.tgz",
"integrity": "sha512-xiLgkD6+lu1B+V5xzp4/JEwTWTDbMFsUkOnUved+udTh3Qg9lvnIa9he5lu/SCOwtLVTzYh2+Ha2SM5WsbpRmg==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-linux-arm64-musl": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-arm64-musl/-/baml-linux-arm64-musl-0.90.0.tgz",
"integrity": "sha512-VVq/bECbNFiSPE52aljL3wj5LzArpTm5DcweT/QlPODLwnUUq/XBCikZu3p4STDzZzk6k05asgqMIrTsrE64wA==",
"cpu": [
"arm64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-linux-x64-gnu": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-x64-gnu/-/baml-linux-x64-gnu-0.90.0.tgz",
"integrity": "sha512-Xdgmy6OHCP4UsAdfT2+8AzNmc94rERJg8nD/tHvPX8M5KKlCNn4hNQCHGJTdNVCBNmD3wzSpDX1IKHxPxMDW3w==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-linux-x64-musl": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-linux-x64-musl/-/baml-linux-x64-musl-0.90.0.tgz",
"integrity": "sha512-8TD2X2NlJ1+XF7IiPJIL6BftlP+lWsKzv81cns4JtodtAFY/3ntmtp4SkwmuzIdw60kMNO4O/wT/bpXUPirnZg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@boundaryml/baml-win32-x64-msvc": {
"version": "0.90.0",
"resolved": "https://registry.npmjs.org/@boundaryml/baml-win32-x64-msvc/-/baml-win32-x64-msvc-0.90.0.tgz",
"integrity": "sha512-vuFVxjkT0UBZwFVNFKGJzvYjSji0ob8Dd0RMSom1cJoyIY7ka4+BCsq99oqSsvnEhNa4WiiLbmuoja3Okd78Vg==",
"cpu": [
"x64"
],
"license": "MIT",
"optional": true,
"os": [
"win32"
],
"engines": {
"node": ">= 10"
}
},
"node_modules/@esbuild/aix-ppc64": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.4.tgz",
@@ -632,6 +770,13 @@
"@noble/hashes": "^1.1.5"
}
},
"node_modules/@scarf/scarf": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@scarf/scarf/-/scarf-1.4.0.tgz",
"integrity": "sha512-xxeapPiUXdZAE3che6f3xogoJPeZgig6omHEy1rIY5WVsB3H2BHNnZH+gHG6x91SCWyQCzWGsuL2Hh3ClO5/qQ==",
"hasInstallScript": true,
"license": "Apache-2.0"
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -3384,6 +3529,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.25.64",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.25.64.tgz",
"integrity": "sha512-hbP9FpSZf7pkS7hRVUrOjhwKJNyampPgtXKc3AN6DsWtoHsg2Sb4SQaS4Tcay380zSwd2VPo9G9180emBACp5g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View File

@@ -11,7 +11,8 @@
"express": "^5.1.0",
"humanlayer": "^0.7.7",
"tsx": "^4.15.0",
"typescript": "^5.0.0"
"typescript": "^5.0.0",
"zod": "^3.25.64"
},
"devDependencies": {
"@types/express": "^5.0.1",

View File

@@ -0,0 +1,53 @@
import { z, ZodSchema } from 'zod';
// Types for A2H API objects matching the new schemas
// Common metadata type
export type Metadata = {
uid: string;
};
// Message sent by agent to a2h server
type MessageSpec<T extends ZodSchema<any>> = {
agentMessage: string; // message from the agent
response_schema?: T; // optional Zod schema for the response
channel_id?: string; // optional channel id
};
export type Message<T extends ZodSchema<any> = ZodSchema<any>> = {
apiVersion: "proto.a2h.dev/v1alpha1";
kind: "Message";
metadata: Metadata;
spec: MessageSpec<T>;
status?: {
humanMessage?: string; // message from the human
response?: T extends ZodSchema<any> ? z.infer<T> : any; // optional, matches spec schema
};
};
export const ApprovalSchema = z.object({
approved: z.boolean(),
comment: z.string().optional(),
});
export type ApprovalRequest = Message<typeof ApprovalSchema>;
export type HumanRequest = Message;
// NewConversation sent by a2h server to agent
type NewConversationSpec = {
user_message: string; // message from the human
channel_id: string; // channel id to use for future conversations
agent_name?: string; // optional agent name or identifier
raw?: Record<string, any>; // optional raw data from the request, e.g. email metadata
};
export type NewConversation = {
apiVersion: "proto.a2h.dev/v1alpha1";
kind: "NewConversation";
metadata: Metadata;
spec: NewConversationSpec;
};
// Optionally, you can add union types for future extensibility
export type A2HEvent<T extends ZodSchema<any> = ZodSchema<any>> = Message<T> | NewConversation;