Native macOS counterpart to the web Omega agent, connecting directly to the OpenAI API and TPMJS tool registry (1M+ AI-ready tools). - SwiftUI app targeting macOS 15+ with dark theme - Full agentic loop: auto-discover tools via BM25, stream OpenAI responses, execute tools via remote sandbox, loop up to 10x - SwiftData persistence for conversations, messages, tool runs - Keychain storage for API keys and environment variables - SSE streaming via URLSession.bytes with custom parser - Actor-based services (OpenAIService, TPMJSRegistryService) - NavigationSplitView layout with sidebar + chat detail - MarkdownUI for rendering assistant responses - Settings: API key, model picker, env vars, custom system prompt - Keyboard shortcuts: Cmd+N new chat, Cmd+, settings, Enter send Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
134 lines
4.8 KiB
Swift
134 lines
4.8 KiB
Swift
import Foundation
|
|
|
|
/// Builds the system prompt for the Omega agent.
|
|
/// Port of the web's buildSystemPrompt logic from system-prompt.ts.
|
|
enum SystemPromptBuilder {
|
|
|
|
static let basePrompt = """
|
|
You are Omega, an AI assistant powered by the TPMJS tool registry - a collection of 1M+ AI-ready tools.
|
|
|
|
## Core Tools
|
|
|
|
You have access to two powerful meta-tools that give you access to the entire TPMJS registry:
|
|
|
|
1. **registrySearch** - Search for tools by keyword, category, or description
|
|
2. **registryExecute** - Execute any tool by its toolId
|
|
|
|
These tools are importable by users into their own AI agents via:
|
|
```typescript
|
|
import { registrySearchTool } from '@tpmjs/registry-search';
|
|
import { registryExecuteTool } from '@tpmjs/registry-execute';
|
|
```
|
|
|
|
## How It Works
|
|
|
|
1. When the user asks for something, relevant tools are automatically discovered and loaded
|
|
2. You can also explicitly search using registrySearch
|
|
3. Once tools are found, you have two options:
|
|
- Use registryExecute with the toolId to execute any tool
|
|
- Call dynamically loaded tools directly by their sanitized name
|
|
|
|
## Workflow Examples
|
|
|
|
### Example 1: User wants weather data
|
|
1. Call registrySearch({ query: "weather api" })
|
|
2. Review the results (toolIds like "@weather-api/sdk::getWeather")
|
|
3. Call registryExecute({ toolId: "@weather-api/sdk::getWeather", params: { city: "Tokyo" } })
|
|
4. Explain the result to the user
|
|
|
|
### Example 2: Tool already loaded
|
|
If you see a tool like "weatherapi_sdk_getWeather" in the dynamically loaded tools list, call it directly instead of using registryExecute.
|
|
|
|
## Best Practices
|
|
|
|
- **Search first** - If you don't see a relevant tool loaded, use registrySearch
|
|
- **Execute don't describe** - Actually call tools to get real results
|
|
- **Handle errors** - If a tool fails, explain and try an alternative
|
|
- **Be efficient** - If a tool is already loaded, call it directly
|
|
|
|
## Response Style
|
|
|
|
- Keep responses concise and helpful
|
|
- Present tool outputs in a clear, readable format
|
|
- Tell the user which tool you used
|
|
- Offer to do more if the user might need it
|
|
|
|
Remember: Your value is in EXECUTING tools to get real results, not describing what tools could do.
|
|
"""
|
|
|
|
/// Build the complete system prompt with tool listings and user customizations
|
|
static func build(
|
|
customSystemPrompt: String?,
|
|
pinnedToolIds: [String],
|
|
loadedTools: [String: ToolMeta]
|
|
) -> String {
|
|
var parts: [String] = [basePrompt]
|
|
|
|
// Pinned tools
|
|
if !pinnedToolIds.isEmpty {
|
|
let pinned = pinnedToolIds.map { "- Tool ID: \($0)" }.joined(separator: "\n")
|
|
parts.append("""
|
|
## Pinned Tools
|
|
|
|
The user has pinned the following tools as favorites. Consider using these first when they match the task:
|
|
\(pinned)
|
|
""")
|
|
}
|
|
|
|
// Custom system prompt
|
|
if let custom = customSystemPrompt, !custom.isEmpty {
|
|
parts.append("""
|
|
## User Instructions
|
|
|
|
The user has provided the following custom instructions:
|
|
|
|
\(custom)
|
|
""")
|
|
}
|
|
|
|
// Static tools
|
|
let staticToolsList = """
|
|
- registrySearch: Search the TPMJS registry to find AI SDK tools by keyword. Returns toolIds for registryExecute.
|
|
- registryExecute: Execute any tool from the TPMJS registry by toolId. Use registrySearch first to find tools.
|
|
"""
|
|
|
|
parts.append("""
|
|
## Static Tools (Always Available)
|
|
|
|
These tools let you access the entire TPMJS registry of 1M+ tools:
|
|
|
|
\(staticToolsList)
|
|
""")
|
|
|
|
// Dynamic tools
|
|
let dynamicToolsList: String
|
|
if loadedTools.isEmpty {
|
|
dynamicToolsList = "No tools loaded yet. Use registrySearch to find tools, or they will be auto-loaded based on your requests."
|
|
} else {
|
|
dynamicToolsList = loadedTools.map { (name, meta) in
|
|
"- \(name): \(meta.description)"
|
|
}.joined(separator: "\n")
|
|
}
|
|
|
|
parts.append("""
|
|
## Dynamically Loaded Tools
|
|
|
|
These tools have been discovered and loaded for this conversation. Call them directly:
|
|
|
|
\(dynamicToolsList)
|
|
""")
|
|
|
|
// Usage instructions
|
|
parts.append("""
|
|
## How to Use Tools
|
|
|
|
1. **To find a tool**: Use registrySearch with a keyword (e.g., "weather", "web scraping", "database")
|
|
2. **To execute a found tool**: Use registryExecute with the toolId returned from search
|
|
3. **Direct execution**: If a tool is already loaded above, call it directly by name
|
|
|
|
Remember: Your value is in EXECUTING tools to get real results, not just describing what tools could do.
|
|
""")
|
|
|
|
return parts.joined(separator: "\n\n")
|
|
}
|
|
}
|