Skip to content

Commit 1a194ce

Browse files
committed
fix: restructure existing agent patterns and fix bugs
1 parent 4bb6dc7 commit 1a194ce

28 files changed

Lines changed: 441 additions & 512 deletions

File tree

frontend/src/lib/agentcore-client/parsers/langgraph.ts

Lines changed: 23 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,19 @@ import type { ChunkParser } from "../types"
66
/**
77
* Parses SSE chunks from LangGraph agents (stream_mode="messages").
88
*
9-
* Each SSE line is a model_dump() of either AIMessageChunk or ToolMessage.
9+
* Uses LangChain's normalized message fields — works with any ChatModel backend.
1010
*
11-
* AIMessageChunk shapes:
12-
* - text: content: [{type:"text", text:"...", index:N}]
13-
* - tool start: content: [{type:"tool_use", id, name, input:{}, index:N}] + tool_calls[]
14-
* - tool delta: content: [{type:"tool_use", partial_json:"...", index:N}] + tool_call_chunks[]
15-
* - stop: response_metadata: {stop_reason:"end_turn"|"tool_use"}
16-
* - last: chunk_position: "last"
17-
*
18-
* ToolMessage shape:
19-
* - type: "tool", content: "result", name: "tool_name", tool_call_id: "id"
11+
* Stream format:
12+
* AIMessageChunk:
13+
* - text: content (string)
14+
* - tool start: tool_call_chunks[{id, name, args}] — first chunk has id + name
15+
* - tool delta: tool_call_chunks[{args}] — subsequent chunks have args only
16+
* - stop: response_metadata.stop_reason
17+
* ToolMessage:
18+
* - type: "tool", content: string, tool_call_id: string
2019
*/
2120

22-
// Track current tool_use_id across chunks (deltas don't carry the id)
21+
// Track current tool_use_id — streaming deltas may omit the id
2322
let currentToolUseId = ""
2423

2524
export const parseLanggraphChunk: ChunkParser = (line, callback) => {
@@ -43,39 +42,29 @@ export const parseLanggraphChunk: ChunkParser = (line, callback) => {
4342

4443
// AIMessageChunk
4544
if (json.type === "AIMessageChunk") {
46-
// Content as plain string (when model responds without tool use)
45+
// Text token — content can be a string or array of text blocks
4746
if (typeof json.content === "string" && json.content) {
4847
callback({ type: "text", content: json.content })
49-
}
50-
51-
// Content as array of blocks (when model uses tools)
52-
if (Array.isArray(json.content)) {
48+
} else if (Array.isArray(json.content)) {
5349
for (const block of json.content) {
54-
// Text token
5550
if (block.type === "text" && block.text) {
5651
callback({ type: "text", content: block.text })
5752
}
53+
}
54+
}
5855

59-
// Tool use start — has id and name
60-
if (block.type === "tool_use" && block.id && block.name) {
61-
currentToolUseId = block.id
62-
callback({
63-
type: "tool_use_start",
64-
toolUseId: block.id,
65-
name: block.name,
66-
})
56+
// Tool calls — streamed via tool_call_chunks (LangChain's standard streaming field)
57+
if (Array.isArray(json.tool_call_chunks)) {
58+
for (const chunk of json.tool_call_chunks) {
59+
if (chunk.id && chunk.name) {
60+
currentToolUseId = chunk.id
61+
callback({ type: "tool_use_start", toolUseId: chunk.id, name: chunk.name })
6762
}
68-
69-
// Tool input streaming — has partial_json
70-
if (
71-
block.type === "tool_use" &&
72-
typeof block.partial_json === "string" &&
73-
block.partial_json
74-
) {
63+
if (typeof chunk.args === "string" && chunk.args) {
7564
callback({
7665
type: "tool_use_delta",
77-
toolUseId: currentToolUseId,
78-
input: block.partial_json,
66+
toolUseId: chunk.id || currentToolUseId,
67+
input: chunk.args,
7968
})
8069
}
8170
}

infra-cdk/config.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ stack_name_base: FAST-stack
55
admin_user_email: # Example: admin@example.com
66

77
backend:
8-
pattern: strands-single-agent # Available patterns: strands-single-agent, langgraph-single-agent, claude-agent-sdk
8+
pattern: strands-single-agent # See patterns/ for available options
9+
# Pattern prefix determines the frontend parser:
10+
# agui-* → AG-UI parser (e.g. agui-strands-agent)
11+
# strands-* → Strands parser (e.g. strands-single-agent)
12+
# langgraph-* → LangGraph parser (e.g. langgraph-single-agent)
13+
# claude-* → Claude parser (e.g. claude-agent-sdk-single-agent)
14+
# The folder name must match exactly and start with the desired parser prefix.
915
deployment_type: docker # Available deployment types: docker (default), zip (not supported for claude-agent-sdk)
1016
network_mode: PUBLIC # Available network modes: PUBLIC (default), VPC
1117

patterns/__init__.py

Whitespace-only changes.

patterns/claude-agent-sdk-multi-agent/agent.py

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,27 @@
11
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
# SPDX-License-Identifier: Apache-2.0
33

4-
from code_int_mcp.server import code_int_mcp_server
4+
import json
5+
import logging
6+
import os
7+
8+
from agents.subagents import get_subagent_definitions
9+
from bedrock_agentcore.runtime import BedrockAgentCoreApp, RequestContext
510
from claude_agent_sdk import (
611
AssistantMessage,
7-
UserMessage,
12+
ClaudeAgentOptions,
13+
ClaudeSDKClient,
14+
ProcessError,
815
ResultMessage,
916
SystemMessage,
10-
ClaudeAgentOptions,
1117
TextBlock,
12-
ToolUseBlock,
13-
ClaudeSDKClient,
1418
ToolResultBlock,
15-
ProcessError,
19+
ToolUseBlock,
20+
UserMessage,
1621
)
17-
from bedrock_agentcore.runtime import BedrockAgentCoreApp, RequestContext
18-
from agents.subagents import get_subagent_definitions
22+
from code_int_mcp.server import code_int_mcp_server
1923
from utils.auth import extract_user_id_from_context, get_gateway_access_token
2024
from utils.ssm import get_ssm_parameter
21-
import logging
22-
import json
23-
import os
2425

2526
logging.basicConfig(level=logging.INFO)
2627
logger = logging.getLogger(__name__)
@@ -49,7 +50,7 @@ async def main(payload, context: RequestContext):
4950
}
5051
return
5152

52-
code_int_session_id = payload.get("code_int_session_id", "")
53+
_code_int_session_id = payload.get("code_int_session_id", "")
5354
claude_session_id = _session_map.get(runtime_session_id)
5455

5556
# Extract user ID securely from validated JWT token
@@ -68,7 +69,9 @@ async def main(payload, context: RequestContext):
6869
gateway_url = get_ssm_parameter(f"/{stack_name}/gateway_url")
6970
access_token = get_gateway_access_token()
7071
except Exception as e:
71-
logger.warning("[AGENT] Gateway not available, continuing without tools: %s", e)
72+
logger.warning(
73+
"[AGENT] Gateway not available, continuing without tools: %s", e
74+
)
7275
gateway_url = None
7376
access_token = None
7477

@@ -107,13 +110,28 @@ def _build_options(resume_id: str | None) -> ClaudeAgentOptions:
107110
mcp_servers=mcp_servers,
108111
model="us.anthropic.claude-opus-4-6-v1",
109112
allowed_tools=allowed_tools,
110-
disallowed_tools=["Bash", "Write", "NotebookEdit", "Edit", "WebFetch", "Read", "Glob", "Grep", "EnterWorktree", "Skill", "TodoWrite", "CronCreate", "CronDelete", "CronList"],
113+
disallowed_tools=[
114+
"Bash",
115+
"Write",
116+
"NotebookEdit",
117+
"Edit",
118+
"WebFetch",
119+
"Read",
120+
"Glob",
121+
"Grep",
122+
"EnterWorktree",
123+
"Skill",
124+
"TodoWrite",
125+
"CronCreate",
126+
"CronDelete",
127+
"CronList",
128+
],
111129
agents=subagents,
112130
resume=resume_id,
113131
thinking={"type": "adaptive"},
114132
cli_path="/usr/bin/claude",
115133
stderr=lambda line: logger.error("claude-code stderr: %s", line),
116-
system_prompt=f"""You are an AI assistant that helps users with code execution and analysis tasks.
134+
system_prompt="""You are an AI assistant that helps users with code execution and analysis tasks.
117135
118136
CRITICAL RULES:
119137
1. You MUST use mcp__codeint__execute_code for ALL Python code execution tasks.
@@ -144,7 +162,9 @@ async def _process_messages(options: ClaudeAgentOptions):
144162
async for msg in client.receive_response():
145163
if isinstance(msg, SystemMessage):
146164
if msg.subtype == "init":
147-
logger.info("Claude session init: %s", msg.data.get("session_id"))
165+
logger.info(
166+
"Claude session init: %s", msg.data.get("session_id")
167+
)
148168
elif isinstance(msg, AssistantMessage):
149169
for block in msg.content:
150170
if isinstance(block, ToolUseBlock):
@@ -153,7 +173,9 @@ async def _process_messages(options: ClaudeAgentOptions):
153173
"current_tool_use": {
154174
"name": block.name,
155175
"input": block.input,
156-
"toolUseId": f"tool-{block.id}" if hasattr(block, "id") else f"tool-{hash(block.name)}",
176+
"toolUseId": f"tool-{block.id}"
177+
if hasattr(block, "id")
178+
else f"tool-{hash(block.name)}",
157179
}
158180
}
159181
elif isinstance(block, TextBlock):
@@ -169,9 +191,11 @@ async def _process_messages(options: ClaudeAgentOptions):
169191
try:
170192
result_data = json.loads(text_content)
171193
if isinstance(result_data, dict):
172-
extracted = result_data.get("code_int_session_id", "")
194+
extracted = result_data.get(
195+
"code_int_session_id", ""
196+
)
173197
if extracted:
174-
code_int_session_id = extracted
198+
_code_int_session_id = extracted
175199
except json.JSONDecodeError:
176200
pass
177201
elif isinstance(msg, ResultMessage):
@@ -185,7 +209,10 @@ async def _process_messages(options: ClaudeAgentOptions):
185209
yield event
186210
except ProcessError:
187211
if claude_session_id:
188-
logger.warning("Resume failed for session %s, starting fresh session", claude_session_id)
212+
logger.warning(
213+
"Resume failed for session %s, starting fresh session",
214+
claude_session_id,
215+
)
189216
async for event in _process_messages(_build_options(None)):
190217
yield event
191218
else:

patterns/claude-agent-sdk-multi-agent/code_int_mcp/client.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22
# SPDX-License-Identifier: Apache-2.0
33

44
import json
5+
import logging
56
import os
67
import time
8+
79
import boto3
810
from botocore.exceptions import ClientError
9-
import logging
11+
1012
from .models import CodeIntExecutionResult
1113

1214
logging.basicConfig(level=logging.INFO)
@@ -17,7 +19,9 @@ class CodeInterpreterClient:
1719
"""Client for AgentCore Code Interpreter."""
1820

1921
def __init__(self, region_name: str = None):
20-
region = region_name or os.environ.get("AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1"))
22+
region = region_name or os.environ.get(
23+
"AWS_REGION", os.environ.get("AWS_DEFAULT_REGION", "us-east-1")
24+
)
2125
self.ci_client = boto3.client("bedrock-agentcore", region_name=region)
2226

2327
def _create_sessionid(self) -> str:

patterns/claude-agent-sdk-multi-agent/code_int_mcp/models.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
"""Data models for Code Interpreter."""
55

6-
from pydantic import BaseModel, Field
76
from typing import Optional
87

8+
from pydantic import BaseModel, Field
9+
910

1011
class CodeIntExecutionResult(BaseModel):
1112
"""Result model for code execution."""

patterns/claude-agent-sdk-multi-agent/code_int_mcp/server.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,13 @@
33

44
"""In-process MCP server for Code Interpreter."""
55

6-
from .client import CodeInterpreterClient
7-
from claude_agent_sdk import tool, create_sdk_mcp_server
8-
from typing import Any
96
import json
107
import logging
8+
from typing import Any
9+
10+
from claude_agent_sdk import create_sdk_mcp_server, tool
11+
12+
from .client import CodeInterpreterClient
1113

1214
logger = logging.getLogger(__name__)
1315

0 commit comments

Comments
 (0)