Skip to content
Merged
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
5 changes: 3 additions & 2 deletions service/app/api/v1/marketplace.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
- GET /my-listings: Get current user's published listings
"""

from typing import Literal
from typing import Any, Literal
from uuid import UUID

from fastapi import APIRouter, Depends, HTTPException, Query
Expand Down Expand Up @@ -76,6 +76,7 @@ class UpdateAgentRequest(BaseModel):
tags: list[str] | None = None
readme: str | None = None
commit_message: str
graph_config: dict[str, Any] | None = None


class ForkRequest(BaseModel):
Expand Down Expand Up @@ -633,7 +634,7 @@ async def update_agent_and_listing(

try:
updated_listing = await marketplace_service.update_agent_and_publish(
marketplace_id, update_data, request.commit_message
marketplace_id, update_data, request.commit_message, request.graph_config
)
if not updated_listing:
raise HTTPException(status_code=404, detail="Marketplace listing not found")
Expand Down
12 changes: 9 additions & 3 deletions service/app/core/marketplace/agent_marketplace_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,10 @@ async def create_snapshot_from_agent(self, agent: Agent, commit_message: str) ->
"tags": agent.tags or [],
"model": agent.model,
"temperature": agent.temperature,
"prompt": agent.prompt,
"prompt": agent.prompt, # Legacy field, kept for backward compat
"require_tool_confirmation": agent.require_tool_confirmation,
"scope": agent.scope,
"graph_config": agent.graph_config, # Source of truth for agent configuration
}

# Serialize MCP server metadata (no credentials)
Expand Down Expand Up @@ -252,11 +253,12 @@ async def fork_agent(self, marketplace_id: UUID, user_id: str, fork_name: str |
tags=config.get("tags", []),
model=config.get("model"),
temperature=config.get("temperature"),
prompt=config.get("prompt"),
prompt=config.get("prompt"), # Legacy field for backward compat
require_tool_confirmation=config.get("require_tool_confirmation", False),
provider_id=None, # User must configure their own provider
knowledge_set_id=None, # Create empty knowledge set
mcp_server_ids=[], # Will link compatible MCPs below
graph_config=config.get("graph_config"), # Restore from snapshot
)

# Create the forked agent
Expand Down Expand Up @@ -518,6 +520,7 @@ async def update_agent_and_publish(
marketplace_id: UUID,
agent_update: AgentMarketplaceUpdate,
commit_message: str,
graph_config: dict[str, Any] | None = None,
) -> AgentMarketplace | None:
"""
Updates the underlying agent and publishes a new version.
Expand All @@ -526,6 +529,7 @@ async def update_agent_and_publish(
marketplace_id: The marketplace listing ID.
agent_update: Data to update (name, description, tags, readme).
commit_message: Description of the update.
graph_config: Optional graph configuration to update.

Returns:
The updated marketplace listing.
Expand All @@ -547,6 +551,7 @@ async def update_agent_and_publish(
description=agent_update.description,
avatar=agent_update.avatar,
tags=agent_update.tags,
graph_config=graph_config, # Update graph_config if provided
)
await self.agent_repo.update_agent(agent.id, update_data)
# Refresh agent to get latest state for snapshot
Expand Down Expand Up @@ -633,8 +638,9 @@ async def pull_listing_update(self, agent_id: UUID, user_id: str) -> Agent:
tags=config.get("tags", []),
model=config.get("model"),
temperature=config.get("temperature"),
prompt=config.get("prompt"),
prompt=config.get("prompt"), # Legacy field for backward compat
require_tool_confirmation=config.get("require_tool_confirmation", False),
graph_config=config.get("graph_config"), # Sync from marketplace
)

updated_agent = await self.agent_repo.update_agent(agent.id, update_data)
Expand Down
26 changes: 26 additions & 0 deletions service/app/core/prompts/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,34 @@ def merge_prompt_configs(base: PromptConfig, override: PromptConfig) -> PromptCo
return PromptConfig.model_validate(base_dict)


def get_display_prompt_from_config(config: dict[str, Any]) -> str | None:
"""
Extract display prompt from snapshot configuration for UI purposes.

Priority:
1. graph_config.prompt_config.custom_instructions
2. Legacy prompt field

Args:
config: The snapshot configuration dict

Returns:
The display prompt string or None if not found
"""
# Priority 1: Check graph_config.prompt_config.custom_instructions
graph_config = config.get("graph_config")
if graph_config:
prompt_config = graph_config.get("prompt_config", {})
if custom_instructions := prompt_config.get("custom_instructions"):
return custom_instructions

# Priority 2: Legacy prompt field
return config.get("prompt")


__all__ = [
"DEFAULT_PROMPT_CONFIG",
"get_prompt_config_from_graph_config",
"merge_prompt_configs",
"get_display_prompt_from_config",
Comment on lines +76 to +105
Copy link

Copilot AI Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The helper function 'get_display_prompt_from_config' is defined and exported but is never imported or used anywhere in the codebase. The frontend implements its own version of this logic in 'AgentMarketplaceDetail.tsx' (the 'getDisplayPrompt' function). Consider either removing this unused backend function or documenting its intended use case for future API endpoints that might need to return formatted display prompts.

Suggested change
def get_display_prompt_from_config(config: dict[str, Any]) -> str | None:
"""
Extract display prompt from snapshot configuration for UI purposes.
Priority:
1. graph_config.prompt_config.custom_instructions
2. Legacy prompt field
Args:
config: The snapshot configuration dict
Returns:
The display prompt string or None if not found
"""
# Priority 1: Check graph_config.prompt_config.custom_instructions
graph_config = config.get("graph_config")
if graph_config:
prompt_config = graph_config.get("prompt_config", {})
if custom_instructions := prompt_config.get("custom_instructions"):
return custom_instructions
# Priority 2: Legacy prompt field
return config.get("prompt")
__all__ = [
"DEFAULT_PROMPT_CONFIG",
"get_prompt_config_from_graph_config",
"merge_prompt_configs",
"get_display_prompt_from_config",
__all__ = [
"DEFAULT_PROMPT_CONFIG",
"get_prompt_config_from_graph_config",
"merge_prompt_configs",

Copilot uses AI. Check for mistakes.
]
6 changes: 4 additions & 2 deletions service/app/schemas/model_tier.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class TierModelCandidate:
TierModelCandidate(
model="Vendor2/Claude-4.5-Opus",
provider_type=ProviderType.GPUGEEK,
priority=1,
is_fallback=True,
priority=99,
capabilities=["reasoning", "creative", "coding"],
description="Best for coding and choose this for most tasks. Exceptional in complex coding, agentic tasks, and reasoning; highly reliable for software engineering.",
),
Expand Down Expand Up @@ -104,7 +105,8 @@ class TierModelCandidate:
TierModelCandidate(
model="qwen3-max",
provider_type=ProviderType.QWEN,
priority=2,
is_fallback=True,
priority=99,
capabilities=["coding", "multilingual"],
description="Choose this if user uses Chinese. Impressive in programming, agent tasks, and multilingual support with high benchmark scores; strong for coding and math.",
),
Expand Down
42 changes: 39 additions & 3 deletions web/src/app/marketplace/AgentMarketplaceDetail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,35 @@ import {
import { HeartIcon as HeartSolidIcon } from "@heroicons/react/24/solid";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { AgentSnapshot } from "@/service/marketplaceService";

// Helper to detect agent type from graph_config
function getAgentType(
graphConfig: Record<string, unknown> | null | undefined,
): string {
if (!graphConfig) return "ReAct";
const metadata = graphConfig.metadata as Record<string, unknown> | undefined;
if (metadata?.builtin_key) return String(metadata.builtin_key);
if (metadata?.pattern) return String(metadata.pattern);
// Check if it has custom nodes beyond the standard react pattern
const nodes = graphConfig.nodes as Array<unknown> | undefined;
if (nodes && nodes.length > 2) return "Custom Graph";
return "ReAct";
}

// Helper to extract display prompt from configuration
function getDisplayPrompt(
config: AgentSnapshot["configuration"],
): string | null {
// Check graph_config.prompt_config.custom_instructions first
const gc = config.graph_config as Record<string, unknown> | undefined;
if (gc?.prompt_config) {
const pc = gc.prompt_config as Record<string, unknown>;
if (pc.custom_instructions) return String(pc.custom_instructions);
}
// Fallback to legacy prompt
return config.prompt || null;
}

interface AgentMarketplaceDetailProps {
marketplaceId: string;
Expand Down Expand Up @@ -303,10 +332,15 @@ export default function AgentMarketplaceDetail({
<div className="space-y-6">
{listing.snapshot ? (
<>
<div className="flex items-center gap-2">
<div className="flex flex-wrap items-center gap-2">
<span className="inline-flex items-center rounded-full bg-indigo-100 px-2.5 py-1 text-xs font-semibold text-indigo-700 dark:bg-indigo-900/30 dark:text-indigo-300">
v{listing.snapshot.version}
</span>
<span className="inline-flex items-center rounded-full bg-purple-100 px-2.5 py-1 text-xs font-semibold text-purple-700 dark:bg-purple-900/30 dark:text-purple-300">
{getAgentType(
listing.snapshot.configuration.graph_config,
)}
</span>
<span className="text-sm text-neutral-500 dark:text-neutral-400">
{listing.snapshot.commit_message}
</span>
Expand All @@ -325,14 +359,16 @@ export default function AgentMarketplaceDetail({
)}

{/* System Prompt */}
{listing.snapshot.configuration.prompt && (
{getDisplayPrompt(listing.snapshot.configuration) && (
<div>
<h3 className="text-sm font-medium text-neutral-700 dark:text-neutral-300">
{t("marketplace.detail.config.systemPrompt")}
</h3>
<div className="mt-2 max-h-64 overflow-y-auto rounded-lg border border-neutral-200 bg-neutral-50 p-3 dark:border-neutral-800 dark:bg-neutral-900">
<pre className="whitespace-pre-wrap text-xs text-neutral-600 dark:text-neutral-400">
{listing.snapshot.configuration.prompt}
{getDisplayPrompt(
listing.snapshot.configuration,
)}
</pre>
</div>
</div>
Expand Down
Loading
Loading