Skip to content

ResumeConversationStream

Re-attaches to the answer that is being written, from the event after Last-Event-ID.

Used by the SDKs after a dropped connection; callers rarely need it directly. When no answer is being written, the stream ends at once.

Not yet available. The SDKs have this operation, but no Caitlyn release serves it yet: a call gets 404. This page describes how it will work. GetAgentVersion is the one v3 operation served today.

HTTP GET /api/v3/conversations/{conversationId}/stream
TypeScript caitlyn.conversations.resume(conversationId, { … })
Python caitlyn.conversations.resume(conversation_id, …)
Go client.Conversations.Resume(ctx, conversationID, &caitlyn.ResumeConversationStreamInput{…})
Rust client.conversations().resume(conversation_id, ResumeConversationStreamInput { … })
Stability Generally available
Since Caitlyn 5.115.0
Retried by the SDKs Yes, on 429, 503 and connection failures (Retries)
Audited As a write: Streams a conversation's answer, which is part of its transcript. The call is refused with 503 when its audit event cannot be recorded.

Member names are shown as they are in TypeScript and on the wire. Python and Rust use snake case (knowledge_base_id) and Go uses exported names (KnowledgeBaseID). conversationId is the method's first argument rather than an input field.

Member Type Sent in Description
conversationId (required) string path The conversation whose answer to re-attach to. 1 to 128 characters.
lastEventId string header Last-Event-ID The id of the last event received. Absent means from the start of the answer. 1 to 128 characters.

A text/event-stream of ConversationEvent events. The stream ends after message.completed, which the SDKs' finalMessage() (Python final_message(), Go FinalMessage(), Rust final_message()) returns; a failure ends it with message.failed, which the SDKs raise as the typed error. See Streaming.

Event Data Description
message.started MessageStarted The answer has started.
phase.changed PhaseChanged The agent moved to another phase of its workflow.
progress.updated ProgressUpdated A progress step started, changed or finished.
text.delta TextDelta A fragment of answer text. The one recorded exception to the noun.past_tense naming rule.
citation.added CitationAdded A citation was added to the answer.
tool.started ToolStarted A tool call started.
tool.finished ToolFinished A tool call finished.
input.requested InputRequested The agent asked the person for input through a widget.
message.completed MessageCompleted The answer is complete. Ends the stream.
message.failed MessageFailed The answer failed. Ends the stream; its data is a problem details object.

Response headers:

Member Type Sent in Description
requestId string header Caitlyn-Request-Id The request's identifier. The same value is in every error body; quote it to support. 1 to 128 characters.
apiVersion string header Caitlyn-Api-Version The server's model version, for example 5.120.0. The SDKs keep the latest value they have seen and refuse to call operations or send members newer than it. 5 to 32 characters. Pattern ^[0-9]+\.[0-9]+\.[0-9]+$.
  • Permission: chat-session-messages.read.
  • Checked on: the conversation named by conversationId.
  • Who can call it: people (signed in, or a personal access token), API clients (machine sign-in), guests (a guest token).

The checks run in this order: who is calling, the workspace and the permission (each refused with 403), then the record (404, which is also what a record the caller may not see returns). Errors explains why.

The SDKs raise the error class for the response's HTTP status, whether or not the operation lists it (Errors). The statuses this operation returns:

Status Error Codes When
400 ValidationError VALIDATION_FAILED, UNKNOWN_FIELD, UNKNOWN_FILTER, UNKNOWN_SORT, INVALID_CURSOR The request is malformed: a member is missing, the wrong type or out of range; it has a member, filter or sort field the operation does not define; or its cursor is not valid for this list.
401 UnauthenticatedError UNAUTHENTICATED No credential was sent, or it is expired or not valid.
403 PermissionDeniedError PERMISSION_DENIED, NOT_AVAILABLE_TO_CREDENTIALS, PRIMARY_WORKSPACE_REQUIRED The caller may not call this operation: it lacks the permission, its credential type may not call it, or the operation is served only on the organisation's primary workspace. Decided before any record is read. A record the caller may not see is 404 instead.
404 NotFoundError NOT_FOUND The record does not exist, or the caller may not see it. The two are deliberately the same, so identifiers cannot be probed.
429 TooManyRequestsError RATE_LIMITED Too many requests; wait for Retry-After and try again. Retried by the SDKs.
500 InternalError INTERNAL An unexpected server failure. Not retried by the SDKs.
503 ServiceUnavailableError SERVICE_UNAVAILABLE A dependency is briefly unavailable, or the request's audit event could not be recorded; wait for Retry-After and try again. Retried by the SDKs.

The answer arrives as a stream of events; the example shows only the request.

import { Caitlyn } from '@caitlyn-ai/sdk';
const caitlyn = new Caitlyn({ workspace: 'your-company.getcaitlyn.ai', auth: { mode: 'token', token: process.env.CAITLYN_TOKEN! } });
const stream = caitlyn.conversations.resume('0193a6f2-7c4e-7b1a-9d2e-3f5a8c1b0e47', {
lastEventId: '0193a6f2-9b10-7c3d-8e44-1f2a3b4c5d6e',
});
for await (const event of stream) {
if (event.type === 'text.delta') process.stdout.write(event.delta);
}
console.log(await stream.finalMessage());
import os
from caitlyn import Caitlyn, TextDeltaEvent
from caitlyn.auth import Token
caitlyn = Caitlyn(workspace="your-company.getcaitlyn.ai", auth=Token(os.environ["CAITLYN_TOKEN"]))
with caitlyn.conversations.resume(
"0193a6f2-7c4e-7b1a-9d2e-3f5a8c1b0e47",
last_event_id="0193a6f2-9b10-7c3d-8e44-1f2a3b4c5d6e",
) as stream:
for event in stream:
if isinstance(event, TextDeltaEvent):
print(event.delta, end="", flush=True)
print(stream.final_message())
package main
import (
"context"
"fmt"
"log"
"os"
caitlyn "github.com/caitlyn-ai/caitlyn-go"
)
func main() {
ctx := context.Background()
client, err := caitlyn.NewClient("your-company.getcaitlyn.ai", caitlyn.Token(os.Getenv("CAITLYN_TOKEN")))
if err != nil {
log.Fatal(err)
}
stream, err := client.Conversations.Resume(ctx, "0193a6f2-7c4e-7b1a-9d2e-3f5a8c1b0e47", &caitlyn.ResumeConversationStreamInput{
LastEventID: caitlyn.Ptr("0193a6f2-9b10-7c3d-8e44-1f2a3b4c5d6e"),
})
if err != nil {
log.Fatal(err)
}
defer stream.Close()
for event, err := range stream.Events() {
if err != nil {
log.Fatal(err)
}
if delta, ok := event.(*caitlyn.TextDelta); ok {
fmt.Print(delta.Delta)
}
}
}
use caitlyn_sdk::{Auth, Client, ConversationEvent, ResumeConversationStreamInput};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new("your-company.getcaitlyn.ai", Auth::token(std::env::var("CAITLYN_TOKEN")?))?;
let mut stream = client
.conversations()
.resume("0193a6f2-7c4e-7b1a-9d2e-3f5a8c1b0e47", ResumeConversationStreamInput {
last_event_id: Some("0193a6f2-9b10-7c3d-8e44-1f2a3b4c5d6e".into()),
})
.await?;
while let Some(event) = stream.next().await {
if let ConversationEvent::TextDelta(delta) = event? {
print!("{}", delta.delta);
}
}
Ok(())
}

The HTTP exchange:

GET /api/v3/conversations/0193a6f2-7c4e-7b1a-9d2e-3f5a8c1b0e47/stream HTTP/1.1
Host: your-company.getcaitlyn.ai
Authorization: Bearer <token>
Accept: text/event-stream
Last-Event-ID: 0193a6f2-9b10-7c3d-8e44-1f2a3b4c5d6e
HTTP/1.1 200
Content-Type: text/event-stream

A conversation that does not exist, or that the caller may not see

Section titled “A conversation that does not exist, or that the caller may not see”
import { Caitlyn, NotFoundError } from '@caitlyn-ai/sdk';
const caitlyn = new Caitlyn({ workspace: 'your-company.getcaitlyn.ai', auth: { mode: 'token', token: process.env.CAITLYN_TOKEN! } });
try {
const stream = caitlyn.conversations.resume('0193a6f2-0000-7000-8000-000000000000', {});
for await (const event of stream) {
if (event.type === 'text.delta') process.stdout.write(event.delta);
}
console.log(await stream.finalMessage());
} catch (err) {
if (!(err instanceof NotFoundError)) throw err;
console.error(err.status, err.code, err.detail, err.requestId);
}
import os
from caitlyn import Caitlyn, NotFoundError, TextDeltaEvent
from caitlyn.auth import Token
caitlyn = Caitlyn(workspace="your-company.getcaitlyn.ai", auth=Token(os.environ["CAITLYN_TOKEN"]))
try:
with caitlyn.conversations.resume("0193a6f2-0000-7000-8000-000000000000") as stream:
for event in stream:
if isinstance(event, TextDeltaEvent):
print(event.delta, end="", flush=True)
print(stream.final_message())
except NotFoundError as err:
print(err.status, err.code, err.detail, err.request_id)
package main
import (
"context"
"errors"
"fmt"
"log"
"os"
caitlyn "github.com/caitlyn-ai/caitlyn-go"
)
func main() {
ctx := context.Background()
client, err := caitlyn.NewClient("your-company.getcaitlyn.ai", caitlyn.Token(os.Getenv("CAITLYN_TOKEN")))
if err != nil {
log.Fatal(err)
}
stream, err := client.Conversations.Resume(ctx, "0193a6f2-0000-7000-8000-000000000000", &caitlyn.ResumeConversationStreamInput{})
if err != nil {
var apiErr *caitlyn.NotFoundError
if errors.As(err, &apiErr) {
fmt.Println(apiErr.Status, apiErr.Code, apiErr.Detail, apiErr.RequestID)
return
}
log.Fatal(err)
}
defer stream.Close()
for event, err := range stream.Events() {
if err != nil {
log.Fatal(err)
}
if delta, ok := event.(*caitlyn.TextDelta); ok {
fmt.Print(delta.Delta)
}
}
}
use caitlyn_sdk::{Auth, Client, ConversationEvent, ResumeConversationStreamInput};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = Client::new("your-company.getcaitlyn.ai", Auth::token(std::env::var("CAITLYN_TOKEN")?))?;
let outcome: Result<(), caitlyn_sdk::Error> = async {
let mut stream = client
.conversations()
.resume("0193a6f2-0000-7000-8000-000000000000", ResumeConversationStreamInput {
..Default::default()
})
.await?;
while let Some(event) = stream.next().await {
if let ConversationEvent::TextDelta(delta) = event? {
print!("{}", delta.delta);
}
}
Ok(())
}
.await;
match outcome {
Err(caitlyn_sdk::Error::NotFound(err)) => eprintln!("{} {} {:?} {:?}", err.status, err.code, err.detail, err.request_id),
other => other?,
}
Ok(())
}

The HTTP exchange:

GET /api/v3/conversations/0193a6f2-0000-7000-8000-000000000000/stream HTTP/1.1
Host: your-company.getcaitlyn.ai
Authorization: Bearer <token>
Accept: text/event-stream
HTTP/1.1 404
Content-Type: application/problem+json
{
"type": "https://build.caitlyn.ai/errors/NOT_FOUND",
"title": "Not found",
"status": 404,
"code": "NOT_FOUND",
"requestId": "01JC9Q2W7R4M0H2T6N1S8V3B62"
}