Add OHS endpoints - #1021
Conversation
| if create == nil { | ||
| return nil, nil, errors.New("hosted agents: create request is required") | ||
| } | ||
| if create.AgentKind == "" || create.AgentKind == HostedAgentKindUnspecified { |
There was a problem hiding this comment.
We will be moving to something like doctl agents start --spec /path/to/agent-spec.yaml for v0, so this arg will be specified in the agent spec file.
There was a problem hiding this comment.
Makes sense, doctl parses the spec and fills agent_kind before calling godo. so this stays as-is since the API still expects it on CreateSession
| if opt != nil { | ||
| q := url.Values{} | ||
| if opt.ReplayFrom != "" { | ||
| q.Set("replay_from", opt.ReplayFrom) |
There was a problem hiding this comment.
Is it possible to store last_seen_event_id in doctl cache (in a local file)? Just wanted to confirm for our backend design, no implementation required as of yet.
There was a problem hiding this comment.
Yes, we can do that
| } | ||
|
|
||
| // ExecInSandbox runs a command inside the session sandbox. | ||
| func (s *HostedAgentsServiceOp) ExecInSandbox(ctx context.Context, sessionID string, body *HostedAgentSandboxExecRequest) (*HostedAgentSandboxExecResponse, *Response, error) { |
There was a problem hiding this comment.
Details on this from a backend perspective are a bit unclear so, this might require another review later.
| } | ||
|
|
||
| client := godo.NewFromToken(token) | ||
| if baseURL := os.Getenv("DIGITALOCEAN_API_URL"); baseURL != "" { |
There was a problem hiding this comment.
I assume this will get removed from examples once api.digitalocean.com/v2/... endpoints are available, because default URL domain is api.digitalocean.com
| type HostedAgentHITLActionKind string | ||
|
|
||
| const ( | ||
| HostedAgentHITLActionUnspecified HostedAgentHITLActionKind = "HITL_ACTION_KIND_UNSPECIFIED" |
There was a problem hiding this comment.
I see these enum values are not enforced, should we even do it?
There was a problem hiding this comment.
I followed the existing pattern since godo does not enforce every enum on the client
Per PR review, OAuth is out of scope for v0 (GitHub creds come from the agent spec). Also simplify sessions base path to /v2/agents/sessions. Co-authored-by: Cursor <[email protected]>
The session SSE stream serializes the SPI canonical event envelope (type/data/timestamp/tenant_id with dot-separated event names), not the proto-style kind/payload/at/team_id shape HostedAgentEvent previously expected. Decode the real wire via a custom UnmarshalJSON and switch the HostedAgentEventKind constants to the dot-separated SPI names, so streamed events are mapped onto Kind/Payload/At/TeamID instead of being dropped. Co-authored-by: Cursor <[email protected]>
Some services (e.g. the hosted-agents harness-api) return errors as
{"error":{"code":...,"message":...}} rather than the top-level
{"message":...} shape. Capture that nested envelope on ErrorResponse and
fall back to its message in Error() so callers see the server's reason
instead of a bare status code.
Co-authored-by: Cursor <[email protected]>
…1067) * hosted agents: add Session.origin product provenance Port MARSOHS-551 origin types onto OHS_endpoints so create/get/list can carry direct|simulation|evaluation provenance. Document that ListSessions omits sim/eval server-side. Co-authored-by: Cursor <[email protected]> * Remove Unreleased CHANGELOG entry for OHS beta work CHANGELOG is reserved for GA releases; OHS ships via the beta process. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
Live session streaming moved off the control plane's
`/v2/agents/sessions/{id}/stream` onto the data plane's
`/v2/agents/sessions/{id}/events`, so point StreamSession there.
The two surfaces are not interchangeable, so the move is a split rather than
a rename:
- Live reads go to `/events`. Delivery is forward-only from the moment of
attach, and the resume cursor rides in the standard `Last-Event-ID`
header instead of a query parameter.
- `ReplayOnly` reads stay on `/stream?replay_only=true`. The control plane
owns the stored event history; the data-plane endpoint holds none, so
sending a replay-only read there would hang on a stream that never
yields the history it was asked for.
The live stream also carries `stream.state` transport control frames
reporting connection health (live / catching_up / degraded / superseded).
They arrive in the same canonical envelope as an event, so they decode
through the same parser; name the kind and its payload so callers can
identify and skip them rather than rendering them as session activity.
Co-Authored-By: Claude Opus 5 <[email protected]>
…dpoint
The previous commit moved live streaming onto the data plane but left
replay-only reads (`doctl agents logs`) on the control plane's
`/stream?replay_only=true`, because the data-plane endpoint held no history
of its own -- it delivered forward-only from the moment of attach.
The data plane now stores and serves that history, so both reads move onto
`/v2/agents/sessions/{id}/events` and the split disappears:
- Live is unchanged: forward-only from attach, connection held open,
ReplayFrom sent as Last-Event-ID.
- ReplayOnly adds `?replay_only=true`. The server writes the session's
stored history and then ends the stream, so the read terminates on its
own rather than blocking on a connection that never closes.
ReplayFrom keeps riding the `replay_from` query parameter on a replay-only
read rather than moving to Last-Event-ID with the live lane. The two are not
the same cursor: on a live attach it is a resume hint the server is free to
widen for context, while on a history read it is an explicit pagination
point, and collapsing them would let a history query silently return events
the caller already has.
`stream.state` frames now appear on both reads. A replay-only read reports
`catching_up` and then simply ends -- it never reaches `live`, since there is
no live tail to join.
Co-Authored-By: Claude Opus 5 <[email protected]>
Co-authored-by: Cursor <[email protected]>
…vents endpoint" This reverts commit 88bd625.
… endpoint" This reverts commit 4a6471b.
* hosted agents: page session history backwards from a cursor harness-api now bounds a cursorless replay to the newest window of a session's history, so a replay-only attach no longer returns everything. Reading older events means walking backwards a page at a time. StreamSession gains Before and Limit, and HasMore reports whether the walk can continue. The server ends a page with a "has_more" SSE comment, which SSEReader dropped along with every other comment, so it grows an OnComment hook to make comment lines observable without dispatching them as events. Co-authored-by: Cursor <[email protected]> * hosted agents: exercise history paging from the stream-session example The example could only replay forwards, so there was no way to try the backward walk by hand. It now reads BEFORE and LIMIT, and closes with the event count, the oldest id seen, and has_more, so the printed cursor can be fed straight back in as the next BEFORE. Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
* hosted agents: add provider OAuth connect endpoints
Add StartProviderAuth (POST /v2/agents/auth/{provider}) and
PollProviderAuth (GET /v2/agents/auth/{provider}/poll) to the
HostedAgents service, with response types, tests, and an example.
These support the team-scoped external-provider (e.g. GitHub) connect
flow used to authorize git operations in hosted agent sessions.
Co-authored-by: Cursor <[email protected]>
* hosted agents: use *Timestamp for provider-auth ExpiresAt
Co-authored-by: Cursor <[email protected]>
---------
Co-authored-by: Cursor <[email protected]>
Co-authored-by: SSharma-10 <[email protected]>
…urface (#1083) The canonical event and input surface is a lossy rendering of what the agent runtimes actually speak, which is the right trade for clients that just want "the agent said something" and the wrong one for a client that speaks the session's own protocol and is trying to reproduce its behaviour faithfully. Three additions let such a client opt into the native frames without changing anything for callers that don't. Outbound: HostedAgentEvent gains SourceEventID, SourceEventType and SourceRaw — the runtime's own event id, its type label, and the exact pre-mapping bytes. Raw payloads meaningfully fatten every event, so they are opt-in behind StreamSessionOptions.IncludeRaw (include_raw=true). Inbound: SendInputRequest and ResolveHITLRequest gain SourceRaw, so the frame an input was reduced from, and a reply the outcome enum cannot express (an elicitation's content, a tool's requested input), survive the trip. Text and Outcome stay required alongside them — they remain what non-native clients send and what the audit trail records. New: RelayRequest (POST .../request) forwards one native request frame and returns the agent's reply verbatim. Where SendInput carries the single message with a canonical meaning, this carries everything else a protocol client needs to ask — for codex, the requests behind interrupts, slash commands and model pickers. A JSON-RPC error object is the agent answering and arrives as a normal reply; an empty reply means the in-sandbox adapter declined the method, which callers must not mistake for an answer. Every field is additive and omitempty, so existing callers see no change on the wire. Co-authored-by: Cursor <[email protected]>
…nt (stack on OHS_endpoints) (#1072) * hosted agents: stream live events from the data-plane /events endpoint Live session streaming moved off the control plane's `/v2/agents/sessions/{id}/stream` onto the data plane's `/v2/agents/sessions/{id}/events`, so point StreamSession there. The two surfaces are not interchangeable, so the move is a split rather than a rename: - Live reads go to `/events`. Delivery is forward-only from the moment of attach, and the resume cursor rides in the standard `Last-Event-ID` header instead of a query parameter. - `ReplayOnly` reads stay on `/stream?replay_only=true`. The control plane owns the stored event history; the data-plane endpoint holds none, so sending a replay-only read there would hang on a stream that never yields the history it was asked for. The live stream also carries `stream.state` transport control frames reporting connection health (live / catching_up / degraded / superseded). They arrive in the same canonical envelope as an event, so they decode through the same parser; name the kind and its payload so callers can identify and skip them rather than rendering them as session activity. Co-Authored-By: Claude Opus 5 <[email protected]> * hosted agents: serve replay-only reads from the data-plane /events endpoint The previous commit moved live streaming onto the data plane but left replay-only reads (`doctl agents logs`) on the control plane's `/stream?replay_only=true`, because the data-plane endpoint held no history of its own -- it delivered forward-only from the moment of attach. The data plane now stores and serves that history, so both reads move onto `/v2/agents/sessions/{id}/events` and the split disappears: - Live is unchanged: forward-only from attach, connection held open, ReplayFrom sent as Last-Event-ID. - ReplayOnly adds `?replay_only=true`. The server writes the session's stored history and then ends the stream, so the read terminates on its own rather than blocking on a connection that never closes. ReplayFrom keeps riding the `replay_from` query parameter on a replay-only read rather than moving to Last-Event-ID with the live lane. The two are not the same cursor: on a live attach it is a resume hint the server is free to widen for context, while on a history read it is an explicit pagination point, and collapsing them would let a history query silently return events the caller already has. `stream.state` frames now appear on both reads. A replay-only read reports `catching_up` and then simply ends -- it never reaches `live`, since there is no live tail to join. Co-Authored-By: Claude Opus 5 <[email protected]> Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Claude Opus 5 <[email protected]> Co-authored-by: Cursor <[email protected]> Co-authored-by: SSharma-10 <[email protected]>
… (MARSOHS-741) (#1086) * Add Hosted Agents Agent Configs client and config-backed session create Add the Agent Configs client (list/get/create/delete + list-sessions-by-config) under /v2/agents/configs, matching the harness-api REST contracts: - Create body is {name, manifest_yaml}; credentials are declared inline in the manifest under spec.secrets and the API rejects the retired secrets / oauth_assignments maps. - HostedAgentConfig carries create-time warnings; credential slots echo only the manifest declaration ({name, source, provider}). Also add CreateSessionFromConfig, which creates a session from an existing immutable config by posting a JSON {name, config_id} body to POST /v2/agents/sessions (the JSON content type selects the server's config-backed create path). Co-authored-by: Cursor <[email protected]> * Address PR review: add ConfigID to HostedAgentSession, tidy CreateAgentConfig doc - Add ConfigID field to HostedAgentSession; harness-api returns config_id on session create/get/list (the durable Agent Config the session was created from) - Reword CreateAgentConfig godoc to reflect that secrets live in the manifest (spec.secrets[].value), not as separate request fields - Assert config_id round-trips in the config-backed session create test Co-authored-by: Cursor <[email protected]> --------- Co-authored-by: Cursor <[email protected]>
Co-authored-by: Cursor <[email protected]>
No description provided.