Skip to content

Commit 1508456

Browse files
authored
Merge pull request #12 from PinataCloud/feat/api-coverage-engines
feat: support OpenClaw and Hermes agents
2 parents 3b3d682 + f84435f commit 1508456

6 files changed

Lines changed: 175 additions & 43 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,11 @@ All notable changes to this project will be documented in this file.
66

77
### 🚀 Features
88

9+
- Support Hermes (and superbuilder) agents: `agents create --engine <openclaw|hermes|superbuilder>`, plus `--user-name`/`--user-email` and `--channels` (JSON) for configuring channels at creation
10+
- Add `agents engines` to list the agent engines enabled for the deployment
11+
- Surface each agent's `engine` in `agents list`/`get`, and per-engine version data in `agents versions`
12+
- `agents channels configure`: add `--enabled` and `--skip-restart`
13+
- `agents tasks create`/`update`: add `--skill` to scope skills to a task
914
- Add `agents templates refs` and `agents templates search-refs` to list/search branches and tags for a repo
1015
- Support submitting/updating templates from a subdirectory via `--path` (monorepos)
1116
- Support `--name`/`--slug` overrides when submitting templates
@@ -15,6 +20,7 @@ All notable changes to this project will be documented in this file.
1520
- Allow flags to be passed after positional arguments (e.g. `files get <id> --network public`); previously such flags were silently ignored
1621
- Fix `agents templates update`/`submit`/`validate` for templates from a branch or subfolder: send the API's `ref` (with `path` for monorepos) instead of the removed `branch` field; `--branch`/`-b` remain as aliases for `--ref`
1722
- Surface server error messages (including validation errors) for the templates API instead of a generic "server returned status N"
23+
- `agents chat` now works with Hermes agents: Hermes sends assistant messages via `data.text` (whole message) instead of `data.delta` (streaming tokens), and the CLI was silently dropping anything without `delta`
1824

1925
### 🚜 Refactor
2026

internal/agents/agents.go

Lines changed: 20 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,18 +26,8 @@ func ListAgents() ([]Agent, error) {
2626
return response.Agents, nil
2727
}
2828

29-
// CreateAgent creates a new agent with the specified parameters.
30-
func CreateAgent(name, description, vibe, emoji, templateID string, skillCids, secretIds []string) (*CreateAgentResponse, error) {
31-
body := CreateAgentBody{
32-
Name: name,
33-
Description: description,
34-
Vibe: vibe,
35-
Emoji: emoji,
36-
SkillCids: skillCids,
37-
SecretIds: secretIds,
38-
TemplateID: templateID,
39-
}
40-
29+
// CreateAgent creates a new agent from the provided request body.
30+
func CreateAgent(body CreateAgentBody) (*CreateAgentResponse, error) {
4131
var response CreateAgentResponse
4232
err := doJSON(http.MethodPost, "", body, &response)
4333
if err != nil {
@@ -54,6 +44,24 @@ func CreateAgent(name, description, vibe, emoji, templateID string, skillCids, s
5444
return &response, nil
5545
}
5646

47+
// ListEngines retrieves the agent engines enabled for this deployment.
48+
func ListEngines() (*EnginesResponse, error) {
49+
var response EnginesResponse
50+
err := doJSON(http.MethodGet, "/engines", nil, &response)
51+
if err != nil {
52+
return nil, err
53+
}
54+
55+
formattedJSON, err := json.MarshalIndent(response.Engines, "", " ")
56+
if err != nil {
57+
return nil, errors.New("failed to format JSON")
58+
}
59+
60+
fmt.Println(string(formattedJSON))
61+
62+
return &response, nil
63+
}
64+
5765
// GetAgent retrieves detailed information about a specific agent.
5866
func GetAgent(agentID string) (*AgentDetailResponse, error) {
5967
var response AgentDetailResponse

internal/agents/channels.go

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,22 @@ func GetChannelStatus(agentID string) (*ChannelStatusResponse, error) {
2727

2828
// ConfigureChannel configures a specific channel for an agent.
2929
// channel must be one of: telegram, slack, discord, whatsapp
30-
func ConfigureChannel(agentID, channel string, botToken, appToken, dmPolicy string, allowFrom []string) error {
30+
func ConfigureChannel(agentID, channel string, botToken, appToken, dmPolicy string, allowFrom []string, enabled *bool, skipRestart bool) error {
3131
body := ConfigureChannelBody{
3232
BotToken: botToken,
3333
AppToken: appToken,
3434
DmPolicy: dmPolicy,
3535
AllowFrom: allowFrom,
36+
Enabled: enabled,
37+
}
38+
39+
path := "/" + agentID + "/channels/" + channel
40+
if skipRestart {
41+
path += "?skipRestart=true"
3642
}
3743

3844
var response ConfigureChannelResponse
39-
err := doJSON(http.MethodPost, "/"+agentID+"/channels/"+channel, body, &response)
45+
err := doJSON(http.MethodPost, path, body, &response)
4046
if err != nil {
4147
return err
4248
}

internal/agents/chat/stream.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,9 +319,13 @@ func StreamChat(ctx context.Context, agentID, token, model, session string, mess
319319

320320
switch payload.Stream {
321321
case "assistant":
322-
// Text delta
322+
// Assistant text. OpenClaw streams token-by-token via
323+
// `data.delta`; Hermes sends the full message in a
324+
// single event via `data.text`. Handle both.
323325
if delta, ok := payload.Data["delta"].(string); ok {
324326
events <- StreamEvent{Type: StreamEventToken, Token: delta}
327+
} else if text, ok := payload.Data["text"].(string); ok {
328+
events <- StreamEvent{Type: StreamEventToken, Token: text}
325329
}
326330

327331
case "tool":

internal/agents/types.go

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Package agents provides type definitions for the Pinata Agents API.
22
package agents
33

4+
import "encoding/json"
5+
46
// AgentStatus represents the possible statuses of an agent.
57
type AgentStatus string
68

@@ -10,6 +12,15 @@ const (
1012
AgentStatusNotRunning AgentStatus = "not_running"
1113
)
1214

15+
// AgentEngine is the container engine that runs an agent.
16+
type AgentEngine string
17+
18+
const (
19+
AgentEngineOpenClaw AgentEngine = "openclaw"
20+
AgentEngineHermes AgentEngine = "hermes"
21+
AgentEngineSuperbuilder AgentEngine = "superbuilder"
22+
)
23+
1324
// Agent represents an AI agent in the system.
1425
type Agent struct {
1526
AgentID string `json:"agentId"`
@@ -18,6 +29,7 @@ type Agent struct {
1829
Description *string `json:"description"`
1930
Vibe *string `json:"vibe"`
2031
Emoji *string `json:"emoji"`
32+
Engine *string `json:"engine"`
2133
GatewayToken string `json:"gatewayToken"`
2234
CreatedAt string `json:"createdAt"`
2335
Status string `json:"status"`
@@ -29,13 +41,28 @@ type Agent struct {
2941

3042
// CreateAgentBody is the request body for creating a new agent.
3143
type CreateAgentBody struct {
32-
Name string `json:"name"`
33-
Description string `json:"description,omitempty"`
34-
Vibe string `json:"vibe,omitempty"`
35-
Emoji string `json:"emoji,omitempty"`
36-
SkillCids []string `json:"skillCids,omitempty"`
37-
SecretIds []string `json:"secretIds,omitempty"`
38-
TemplateID string `json:"templateId,omitempty"`
44+
Name string `json:"name"`
45+
Description string `json:"description,omitempty"`
46+
Vibe string `json:"vibe,omitempty"`
47+
Emoji string `json:"emoji,omitempty"`
48+
Engine string `json:"engine,omitempty"`
49+
SkillCids []string `json:"skillCids,omitempty"`
50+
SecretIds []string `json:"secretIds,omitempty"`
51+
TemplateID string `json:"templateId,omitempty"`
52+
UserName string `json:"userName,omitempty"`
53+
UserEmail string `json:"userEmail,omitempty"`
54+
Channels json.RawMessage `json:"channels,omitempty"`
55+
}
56+
57+
// EngineOption describes an agent engine the deployment supports.
58+
type EngineOption struct {
59+
ID string `json:"id"`
60+
Label string `json:"label"`
61+
}
62+
63+
// EnginesResponse is the response from listing available agent engines.
64+
type EnginesResponse struct {
65+
Engines []EngineOption `json:"engines"`
3966
}
4067

4168
// CreateAgentResponse is the response from creating a new agent.
@@ -79,14 +106,17 @@ type EnvVarDef struct {
79106

80107
// Skill represents a skill in the library.
81108
type Skill struct {
82-
SkillID string `json:"skillId"`
83-
SkillCid string `json:"skillCid"`
84-
Name string `json:"name"`
85-
Description *string `json:"description"`
86-
CreatedAt string `json:"createdAt"`
87-
UserID string `json:"userId"`
88-
EnvVars []EnvVarDef `json:"envVars"`
89-
FileID *string `json:"fileId"`
109+
SkillID string `json:"skillId"`
110+
SkillCid string `json:"skillCid"`
111+
Name string `json:"name"`
112+
Description *string `json:"description"`
113+
CreatedAt string `json:"createdAt"`
114+
UserID string `json:"userId"`
115+
EnvVars []EnvVarDef `json:"envVars"`
116+
FileID *string `json:"fileId"`
117+
HubSlug *string `json:"hubSlug"`
118+
LatestVersion string `json:"latestVersion,omitempty"`
119+
Verified bool `json:"verified,omitempty"`
90120
}
91121

92122
// CreateSkillBody is the request body for creating a skill.
@@ -134,8 +164,10 @@ type AddSkillsBody struct {
134164

135165
// AddSkillsResponse is the response from adding skills to an agent.
136166
type AddSkillsResponse struct {
137-
Success bool `json:"success"`
138-
Attached int `json:"attached"`
167+
Success bool `json:"success"`
168+
Attached int `json:"attached"`
169+
FilesCopied int `json:"filesCopied,omitempty"`
170+
CopyErrors []string `json:"copyErrors,omitempty"`
139171
}
140172

141173
// --- Secrets ---
@@ -256,6 +288,7 @@ type ConfigureChannelBody struct {
256288
AppToken string `json:"appToken,omitempty"`
257289
DmPolicy string `json:"dmPolicy,omitempty"`
258290
AllowFrom []string `json:"allowFrom,omitempty"`
291+
Enabled *bool `json:"enabled,omitempty"`
259292
}
260293

261294
// ConfigureChannelResponse is the response from configuring a channel.
@@ -494,6 +527,7 @@ type CreateTaskBody struct {
494527
WakeMode WakeMode `json:"wakeMode,omitempty"`
495528
Payload TaskPayload `json:"payload"`
496529
Delivery *TaskDelivery `json:"delivery,omitempty"`
530+
Skills []string `json:"skills,omitempty"`
497531
}
498532

499533
// UpdateTaskBody is the request body for updating a cron job.
@@ -504,6 +538,7 @@ type UpdateTaskBody struct {
504538
Schedule *TaskSchedule `json:"schedule,omitempty"`
505539
Payload *TaskPayload `json:"payload,omitempty"`
506540
Delivery *TaskDelivery `json:"delivery,omitempty"`
541+
Skills []string `json:"skills,omitempty"`
507542
}
508543

509544
// ToggleTaskBody is the request body for toggling a cron job.
@@ -701,8 +736,19 @@ type UpdateApplyResponse struct {
701736

702737
// VersionsResponse is the response from getting available agent versions.
703738
type VersionsResponse struct {
704-
CurrentVersion string `json:"currentVersion"`
705-
AvailableVersions []string `json:"availableVersions"`
739+
CurrentVersion *string `json:"currentVersion"`
740+
Engine *string `json:"engine"`
741+
AvailableVersions []string `json:"availableVersions"`
742+
AvailableVersionsPerEngine *AvailableVersionsPerEngine `json:"availableVersionsPerEngine,omitempty"`
743+
PinclawAgentVersion *float64 `json:"pinclawAgentVersion,omitempty"`
744+
CompatibilityMatrix []interface{} `json:"compatibilityMatrix,omitempty"`
745+
}
746+
747+
// AvailableVersionsPerEngine lists available agent versions grouped by engine.
748+
type AvailableVersionsPerEngine struct {
749+
Openclaw []string `json:"openclaw"`
750+
Hermes []string `json:"hermes"`
751+
Superbuilder []string `json:"superbuilder"`
706752
}
707753

708754
// --- ClawHub (Skills Marketplace) ---

0 commit comments

Comments
 (0)