Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,053 changes: 1,053 additions & 0 deletions API_SPEC.md

Large diffs are not rendered by default.

117 changes: 116 additions & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ paws_common = { path = "crates/paws_common" }
paws_domain = { path = "crates/paws_domain" }
paws_infra = { path = "crates/paws_infra" }
paws_repo = { path = "crates/paws_repo" }
paws_server = { path = "crates/paws_server" }
paws_services = { path = "crates/paws_services" }
paws_tool_macros = { path = "crates/paws_tool_macros" }

123 changes: 123 additions & 0 deletions api_demo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
#!/bin/bash
# API Demo Script for Paws Server
# This demonstrates the task-based API for the web frontend

set -e

BASE_URL="${1:-http://localhost:3010}"
echo "=== Paws API Demo ==="
echo "Server: $BASE_URL"
echo ""

# 1. Health check
echo "1. Health Check"
echo "GET /api/health"
curl -s "$BASE_URL/api/health"
echo -e "\n"

# 2. Get environment info
echo "2. Get Environment"
echo "GET /api/env"
curl -s "$BASE_URL/api/env" | jq '.cwd'
echo ""

# 3. List available agents
echo "3. List Agents"
echo "GET /api/agents"
curl -s "$BASE_URL/api/agents" | jq '.[].id'
echo ""

# 4. Set active agent
echo "4. Set Active Agent"
echo "POST /api/config/active-agent"
curl -s -X POST "$BASE_URL/api/config/active-agent" \
-H "Content-Type: application/json" \
-d '{"agent_id": "paws"}'
echo -e "\n"

# 5. Create a conversation
echo "5. Create Conversation"
echo "POST /api/conversations"
CONV_ID=$(uuidgen)
curl -s -X POST "$BASE_URL/api/conversations" \
-H "Content-Type: application/json" \
-d "{\"id\": \"$CONV_ID\", \"title\": \"API Demo Conversation\"}"
echo -e "\n"
echo "Conversation ID: $CONV_ID"
echo ""

# 6. List conversations
echo "6. List Conversations"
echo "GET /api/conversations"
curl -s "$BASE_URL/api/conversations" | jq '.[0]'
echo ""

# 7. Create a task (submit a message for processing)
echo "7. Create Task"
echo "POST /api/tasks"
TASK_RESP=$(curl -s -X POST "$BASE_URL/api/tasks" \
-H "Content-Type: application/json" \
-d "{\"conversation_id\": \"$CONV_ID\", \"message\": \"What is 2+2? Just give me the number.\"}")
echo "$TASK_RESP" | jq '.'
TASK_ID=$(echo "$TASK_RESP" | jq -r '.task_id')
echo "Task ID: $TASK_ID"
echo ""

# 8. Get task status
echo "8. Get Task Status (polling...)"
echo "GET /api/tasks/:id"
for i in {1..10}; do
STATUS=$(curl -s "$BASE_URL/api/tasks/$TASK_ID")
STATUS_TYPE=$(echo "$STATUS" | jq -r '.status.type')
echo " Status: $STATUS_TYPE"
if [ "$STATUS_TYPE" = "completed" ] || [ "$STATUS_TYPE" = "failed" ]; then
break
fi
sleep 2
done
echo "$STATUS" | jq '.'
echo ""

# 9. Get task events (for reconnection)
echo "9. Get Task Events"
echo "GET /api/tasks/:id/events"
curl -s "$BASE_URL/api/tasks/$TASK_ID/events" | jq '.'
echo ""

# 10. List all tasks
echo "10. List All Tasks"
echo "GET /api/tasks"
curl -s "$BASE_URL/api/tasks" | jq '.[].status.type'
echo ""

# 11. Get conversation messages
echo "11. Get Conversation Messages"
echo "GET /api/conversations/:id"
curl -s "$BASE_URL/api/conversations/$CONV_ID" | jq '.messages[-1]'
echo ""

# 12. Test SSE stream (for 5 seconds)
echo "12. SSE Stream Test (5 seconds)"
echo "GET /api/tasks/:id/stream"
timeout 5 curl -s -N "$BASE_URL/api/tasks/$TASK_ID/stream" 2>/dev/null | head -5 || echo "(stream ended or no new events)"
echo ""

# 13. Get available models
echo "13. List Models"
echo "GET /api/models"
curl -s "$BASE_URL/api/models" | jq '.[0]'
echo ""

# 14. Get available skills
echo "14. List Skills"
echo "GET /api/skills"
curl -s "$BASE_URL/api/skills" | jq '.[].name'
echo ""

# 15. Cleanup - Delete conversation
echo "15. Delete Conversation"
echo "DELETE /api/conversations/:id"
curl -s -X DELETE "$BASE_URL/api/conversations/$CONV_ID"
echo -e "\n"

echo "=== Demo Complete ==="
10 changes: 10 additions & 0 deletions crates/paws_api/src/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ pub trait API: Sync + Send {
/// Gets a provider by ID
async fn get_provider(&self, id: &ProviderId) -> Result<AnyProvider>;

/// Gets models for a specific provider
async fn get_provider_models(&self, id: &ProviderId) -> Result<Vec<Model>>;

/// Executes a chat request and returns a stream of responses
async fn chat(&self, chat: ChatRequest) -> Result<MpscStream<Result<ChatResponse>>>;

Expand All @@ -55,6 +58,13 @@ pub trait API: Sync + Send {
/// Returns the conversation with the given ID
async fn conversation(&self, conversation_id: &ConversationId) -> Result<Option<Conversation>>;

/// Lists conversation summaries for the active workspace (lightweight, no
/// context)
async fn get_conversation_summaries(
&self,
limit: Option<usize>,
) -> Result<Vec<ConversationSummary>>;

/// Lists all conversations for the active workspace
async fn get_conversations(&self, limit: Option<usize>) -> Result<Vec<Conversation>>;

Expand Down
21 changes: 21 additions & 0 deletions crates/paws_api/src/paws_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,16 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra + SkillRepository + AppConf
.ok_or_else(|| Error::provider_not_available(id.clone()))?)
}

async fn get_provider_models(&self, id: &ProviderId) -> Result<Vec<Model>> {
let provider = self.get_provider(id).await?;
// Only configured URL providers can fetch models
if let Some(configured) = provider.into_configured() {
Ok(self.services.models(configured).await?)
} else {
Ok(vec![])
}
}

async fn chat(
&self,
chat: ChatRequest,
Expand Down Expand Up @@ -143,6 +153,17 @@ impl<A: Services, F: CommandInfra + EnvironmentInfra + SkillRepository + AppConf
.unwrap_or_default())
}

async fn get_conversation_summaries(
&self,
limit: Option<usize>,
) -> anyhow::Result<Vec<ConversationSummary>> {
Ok(self
.services
.get_conversation_summaries(limit)
.await?
.unwrap_or_default())
}

async fn last_conversation(&self) -> anyhow::Result<Option<Conversation>> {
self.services.last_conversation().await
}
Expand Down
16 changes: 16 additions & 0 deletions crates/paws_app/src/services.rs
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,13 @@ pub trait ConversationService: Send + Sync {
F: FnOnce(&mut Conversation) -> T + Send,
T: Send;

/// Find conversation summaries with optional limit (lightweight, no
/// context)
async fn get_conversation_summaries(
&self,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<paws_domain::ConversationSummary>>>;

/// Find conversations with optional limit
async fn get_conversations(
&self,
Expand Down Expand Up @@ -564,6 +571,15 @@ impl<I: Services> ConversationService for I {
self.conversation_service().get_conversations(limit).await
}

async fn get_conversation_summaries(
&self,
limit: Option<usize>,
) -> anyhow::Result<Option<Vec<paws_domain::ConversationSummary>>> {
self.conversation_service()
.get_conversation_summaries(limit)
.await
}

async fn last_conversation(&self) -> anyhow::Result<Option<Conversation>> {
self.conversation_service().last_conversation().await
}
Expand Down
Loading
Loading