Komunikasi dengan Haijun Managed Agents berbasis event. Anda mengirim event pengguna ke agen, dan menerima kembali event agen dan event sesi untuk melacak status.
Jenis event
Event mengalir dalam dua arah.
- Event pengguna dan event sistem adalah yang Anda kirim ke agen: event
user.*memulai sesi dan mengarahkannya seiring berjalannya sesi;system.messagemenambahkan konteks tingkat sistem yang berlaku untuk giliran yang menyertainya dan semua giliran berikutnya.
- Event sesi, event span, dan event agen dikirim kepada Anda untuk observabilitas terhadap status sesi dan kemajuan agen Anda. Koneksi stream yang memilih ikut serta juga menerima delta event.
String jenis event sesi, span, agen, pengguna, dan sistem mengikuti konvensi penamaan {domain}.{action}. Event pratinjau delta khusus stream (event_start, event_delta) adalah pengecualiannya. Lihat Jenis event di referensi untuk katalog lengkapnya. Jenis event webhook terpisah, dan beberapa namanya berbeda dari nama di stream (misalnya, session.status_idled alih-alih session.status_idle).
Setiap event yang dipersistensi menyertakan timestamp processed_at yang ditetapkan saat event selesai diproses. Pada event yang Anda kirim, processed_at bernilai null selama event masih mengantre di belakang event sebelumnya. Pengecualiannya adalah user.define_outcome, user.custom_tool_result, dan user.tool_result, yang diproses saat diterima dan digemakan kembali dengan processed_at yang sudah terisi.
Mengintegrasikan event
Mengirim event
Kirim event user.message untuk memulai atau melanjutkan pekerjaan agen:
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [
{"type": "text", "text": "Analyze the performance of the sort function in utils.py"}
]
}
]
}
EOF ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: user.message
content:
- type: text
text: Analyze the performance of the sort function in utils.py
YAML client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Analyze the performance of the sort function in utils.py",
},
],
},
],
) await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Analyze the performance of the sort function in utils.py",
},
],
},
],
}); await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Analyze the performance of the sort function in utils.py",
},
],
},
],
}); if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: "Analyze the performance of the sort function in utils.py",
},
}},
},
}},
}); err != nil {
panic(err)
} client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Analyze the performance of the sort function in utils.py")
.build())
.build()); $client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Analyze the performance of the sort function in utils.py',
],
],
],
],
); client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Analyze the performance of the sort function in utils.py"
}
]
}
]
)Kirim event user.interrupt untuk menghentikan agen di tengah eksekusi, lalu lanjutkan dengan event user.message untuk mengarahkannya ulang:
# Agen sedang menganalisis sebuah file...
# Interupsi dengan arahan baru:
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{"type": "user.interrupt"},
{
"type": "user.message",
"content": [
{"type": "text", "text": "Instead, focus on fixing the bug in line 42."}
]
}
]
}
EOF # Agen sedang menganalisis sebuah file...
# Interupsi dengan arahan baru:
ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: user.interrupt
- type: user.message
content:
- type: text
text: Instead, focus on fixing the bug in line 42.
YAML # Agen sedang menganalisis sebuah file...
# Interupsi dengan arahan baru:
client.beta.sessions.events.send(
session.id,
events=[
{"type": "user.interrupt"},
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Instead, focus on fixing the bug in line 42.",
},
],
},
],
) // Agen sedang menganalisis sebuah file...
// Interupsi dengan arahan baru:
await client.beta.sessions.events.send(session.id, {
events: [
{ type: "user.interrupt" },
{
type: "user.message",
content: [
{
type: "text",
text: "Instead, focus on fixing the bug in line 42.",
},
],
},
],
}); // Agen sedang menganalisis sebuah file...
// Interupsi dengan arahan baru:
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserInterruptEventParams
{
Type = BetaManagedAgentsUserInterruptEventParamsType.UserInterrupt,
},
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Instead, focus on fixing the bug in line 42.",
},
],
},
],
}); // Agen sedang menganalisis sebuah file...
// Interupsi dengan arahan baru:
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{
{
OfUserInterrupt: &juglow.BetaManagedAgentsUserInterruptEventParams{
Type: juglow.BetaManagedAgentsUserInterruptEventParamsTypeUserInterrupt,
},
},
{
OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: "Instead, focus on fixing the bug in line 42.",
},
}},
},
},
},
}); err != nil {
panic(err)
} // Agen sedang menganalisis sebuah file...
// Interupsi dengan arahan baru:
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserInterruptEventParams.builder()
.type(BetaManagedAgentsUserInterruptEventParams.Type.USER_INTERRUPT)
.build())
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Instead, focus on fixing the bug in line 42.")
.build())
.build()); // Agen sedang menganalisis sebuah file...
// Interupsi dengan arahan baru:
$client->beta->sessions->events->send(
$session->id,
events: [
['type' => 'user.interrupt'],
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Instead, focus on fixing the bug in line 42.',
],
],
],
],
); # Agen sedang menganalisis sebuah file...
# Interupsi dengan arahan baru:
client.beta.sessions.events.send_(
session.id,
events: [
{type: "user.interrupt"},
{
type: "user.message",
content: [
{type: "text", text: "Instead, focus on fixing the bug in line 42."}
]
}
]
)Panggilan tersebut kembali segera setelah event diantrekan, dan processed_at milik interupsi tetap null hingga agen menerapkannya. Respons model yang sedang berlangsung berhenti seketika. Interupsi dapat memerlukan waktu lebih lama untuk diterapkan saat pemanggilan alat sedang berjalan, dan sesi tetap running hingga interupsi diterapkan. Event user.interrupt kemudian muncul di stream, dan giliran yang diinterupsi berakhir dengan event session.status_idle. stop_reason-nya adalah end_turn, nilai yang sama dengan giliran yang selesai dengan sendirinya; tidak ada stop reason khusus untuk interupsi. Agen memulai giliran berikutnya dengan user.message yang Anda kirim setelah interupsi.
Streaming event
Lakukan streaming event dari sesi untuk menerima pembaruan real-time saat agen bekerja. Hanya event yang dipancarkan setelah stream dibuka yang dikirimkan, jadi buka stream sebelum mengirim event untuk menghindari race condition.
# Buka stream terlebih dahulu, lalu kirim pesan pengguna
exec {stream}< <(
curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "accept: text/event-stream"
)
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- >/dev/null <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [{"type": "text", "text": "Summarize the repo README"}]
}
]
}
EOF
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
case $(jq -r '.type' <<<"$event_json") in
agent.message)
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
;;
session.status_idle)
break
;;
session.error)
printf '\n[Error: %s]\n' "$(jq -r '.error.message // "unknown"' <<<"$event_json")"
break
;;
esac
done
exec {stream}<&- # Alur kerja ini tidak cocok dijadikan perintah shell sekali jalan.
# Gunakan salah satu contoh SDK dalam grup kode ini sebagai gantinya. # Buka stream terlebih dahulu, lalu kirim pesan pengguna
with client.beta.sessions.events.stream(session.id) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Summarize the repo README"}],
},
],
)
for event in stream:
match event.type:
case "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="")
case "session.status_idle":
break
case "session.error":
error_message = event.error.message if event.error else "unknown"
print(f"\n[Error: {error_message}]")
break // Buka stream terlebih dahulu, lalu kirim pesan pengguna
const stream = await client.beta.sessions.events.stream(session.id);
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "Summarize the repo README" }]
}
]
});
events: for await (const event of stream) {
switch (event.type) {
case "agent.message":
for (const block of event.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
break;
case "session.status_idle":
break events;
case "session.error":
console.log(`\n[Error: ${event.error?.message ?? "unknown"}]`);
break events;
}
} // Buka stream terlebih dahulu, lalu kirim pesan pengguna
using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Summarize the repo README",
},
],
},
],
});
await foreach (var streamEvent in stream.Enumerate())
{
if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
if (block.Value is BetaManagedAgentsTextBlock textBlock)
{
Console.Write(textBlock.Text);
}
}
}
else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
break;
}
else if (streamEvent.Value is BetaManagedAgentsSessionErrorEvent error)
{
Console.WriteLine($"\n[Error: {error.Error?.Message ?? "unknown"}]");
break;
}
} // Buka stream terlebih dahulu, lalu kirim pesan pengguna
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{})
defer stream.Close()
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: "Summarize the repo README",
},
}},
},
}},
}); err != nil {
panic(err)
}
events:
for stream.Next() {
switch event := stream.Current().AsAny().(type) {
case juglow.BetaManagedAgentsAgentMessageEvent:
// daftar bertipe konkret: BetaManagedAgentsTextBlock
for _, block := range event.Content {
fmt.Print(block.Text)
}
case juglow.BetaManagedAgentsSessionStatusIdleEvent:
break events
case juglow.BetaManagedAgentsSessionErrorEvent:
fmt.Printf("\n[Error: %s]\n", cmp.Or(event.Error.Message, "unknown"))
break events
}
}
if err := stream.Err(); err != nil {
panic(err)
} // Buka stream terlebih dahulu, lalu kirim pesan pengguna
try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Summarize the repo README")
.build())
.build()
);
Iterable<BetaManagedAgentsStreamSessionEvents> events = stream.stream()::iterator;
events:
for (var event : events) {
switch (event.type().value()) {
case AGENT_MESSAGE -> event.asAgentMessage().content().forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text())));
case SESSION_STATUS_IDLE -> {
break events;
}
case SESSION_ERROR -> {
// Field `message` ada di semua varian error; baca dari JSON mentah.
var errorMessage =
event.asSessionError().error()._json().orElse(null) instanceof JsonObject json
? json.values().get("message").asStringOrThrow()
: "unknown";
IO.println("\n[Error: " + errorMessage + "]");
break events;
}
}
}
} // Buka stream terlebih dahulu, lalu kirim pesan pengguna
$stream = $client->beta->sessions->events->streamStream($session->id);
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [['type' => 'text', 'text' => 'Summarize the repo README']],
],
],
);
foreach ($stream as $event) {
match (true) {
$event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent => array_walk(
$event->content,
static fn ($block) => $block instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsTextBlock ? print($block->text) : null,
),
$event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionErrorEvent => printf("\n[Error: %s]", $event->error?->message ?? 'unknown'),
default => null,
};
if ($event->type === 'session.status_idle' || $event->type === 'session.error') {
break;
}
}
$stream->close(); # Buka stream terlebih dahulu, lalu kirim pesan pengguna
stream = client.beta.sessions.events.stream_events(session.id)
client.beta.sessions.events.send_(
session.id,
events: [{
type: "user.message",
content: [{type: "text", text: "Summarize the repo README"}]
}]
)
stream.each do |event|
case event
when Juglow::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
event.content.each { print it.text }
when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
break
when Juglow::Beta::Sessions::BetaManagedAgentsSessionErrorEvent
puts "\n[Error: #{event.error&.message || "unknown"}]"
break
else
# abaikan tipe event lainnya
end
endUntuk menyambung kembali ke sesi yang sudah ada tanpa melewatkan event:
- Buka stream baru.
- Daftarkan riwayat event lengkap untuk mengisi awal sekumpulan ID event yang sudah terlihat.
- Ikuti stream langsung, dengan melewati event apa pun yang sudah dikembalikan oleh daftar riwayat.
exec {stream}< <(
curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "accept: text/event-stream"
)
# Stream terbuka dan melakukan buffering. Tampilkan riwayat sebelum mengikuti event langsung.
declare -A seen_event_ids
while IFS= read -r event_id; do
seen_event_ids[$event_id]=1
done < <(
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" | jq -r '.data[].id'
)
# Ikuti event langsung, lewati yang sudah pernah dilihat
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
event_id=$(jq -r '.id' <<<"$event_json")
[[ -n ${seen_event_ids[$event_id]+seen} ]] && continue
seen_event_ids[$event_id]=1
case $(jq -r '.type' <<<"$event_json") in
agent.message)
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
;;
session.status_idle)
break
;;
esac
done
exec {stream}<&- # Alur kerja ini tidak cocok dijadikan perintah shell sekali jalan.
# Gunakan salah satu contoh SDK dalam grup kode ini sebagai gantinya. with client.beta.sessions.events.stream(session.id) as stream:
# Stream terbuka dan sedang buffering. Tampilkan riwayat sebelum mengikuti event langsung.
history = client.beta.sessions.events.list(session.id)
seen_event_ids = {past_event.id for past_event in history}
# Ikuti event langsung, lewati yang sudah pernah terlihat
for event in stream:
if event.type == "event_start" or event.type == "event_delta":
# Pratinjau delta tidak diaktifkan pada koneksi ini.
continue
if event.id in seen_event_ids:
continue
seen_event_ids.add(event.id)
match event.type:
case "agent.message":
for block in event.content:
if block.type == "text":
print(block.text, end="")
case "session.status_idle":
break const seenEventIds = new Set<string>();
const stream = await client.beta.sessions.events.stream(session.id);
// Stream terbuka dan melakukan buffering. Tampilkan riwayat sebelum mengikuti event langsung.
for await (const event of client.beta.sessions.events.list(session.id)) {
seenEventIds.add(event.id);
}
// Ikuti event langsung, lewati yang sudah terlihat
tail: for await (const event of stream) {
// Event pratinjau (event_start/event_delta) tidak membawa id tingkat atas
if (event.type === "event_start" || event.type === "event_delta") continue;
if (seenEventIds.has(event.id)) continue;
seenEventIds.add(event.id);
switch (event.type) {
case "agent.message":
for (const block of event.content) {
if (block.type === "text") {
process.stdout.write(block.text);
}
}
break;
case "session.status_idle":
break tail;
}
} using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(session.ID);
// Stream terbuka dan sedang buffering. Tampilkan riwayat sebelum tailing live.
HashSet<string> seenEventIds = [];
var history = await client.Beta.Sessions.Events.List(session.ID);
await foreach (var pastEvent in history.Paginate())
{
seenEventIds.Add(pastEvent.ID);
}
// Tail event live, lewati yang sudah pernah dilihat
await foreach (var streamEvent in stream.Enumerate())
{
if (!seenEventIds.Add(streamEvent.ID))
{
continue;
}
if (streamEvent.Value is BetaManagedAgentsAgentMessageEvent message)
{
foreach (var block in message.Content)
{
if (block.Value is BetaManagedAgentsTextBlock textBlock)
{
Console.Write(textBlock.Text);
}
}
}
else if (streamEvent.Value is BetaManagedAgentsSessionStatusIdleEvent)
{
break;
}
} stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{})
defer stream.Close()
// Stream terbuka dan sedang buffering. Tampilkan riwayat sebelum tailing live.
seenEventIDs := map[string]struct{}{}
history := client.Beta.Sessions.Events.ListAutoPaging(ctx, session.ID, juglow.BetaSessionEventListParams{})
for history.Next() {
seenEventIDs[history.Current().ID] = struct{}{}
}
if err := history.Err(); err != nil {
panic(err)
}
// Tail event live, lewati yang sudah pernah dilihat
tail:
for stream.Next() {
event := stream.Current()
if _, seen := seenEventIDs[event.ID]; seen {
continue
}
seenEventIDs[event.ID] = struct{}{}
switch event := event.AsAny().(type) {
case juglow.BetaManagedAgentsAgentMessageEvent:
// daftar bertipe konkret: BetaManagedAgentsTextBlock
for _, block := range event.Content {
fmt.Print(block.Text)
}
case juglow.BetaManagedAgentsSessionStatusIdleEvent:
break tail
}
}
if err := stream.Err(); err != nil {
panic(err)
} try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
// Stream terbuka dan sedang buffering. Tampilkan riwayat sebelum tailing live.
// Setiap varian event membawa `id`; baca dari JSON mentah untuk dedup lintas varian.
var seenEventIds = new HashSet<String>();
for (var pastEvent : client.beta().sessions().events().list(session.id()).autoPager()) {
if (pastEvent._json().orElseThrow() instanceof JsonObject json) {
seenEventIds.add(json.values().get("id").asStringOrThrow());
}
}
// Tail event live; Set.add mengembalikan false untuk ID yang sudah dilihat, melewati replay.
stream.stream()
.filter(event -> event._json().orElseThrow() instanceof JsonObject json
&& seenEventIds.add(json.values().get("id").asStringOrThrow()))
.takeWhile(event -> !event.isSessionStatusIdle())
.filter(BetaManagedAgentsStreamSessionEvents::isAgentMessage)
.forEach(event -> event.asAgentMessage().content()
.forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text()))));
} $stream = $client->beta->sessions->events->streamStream($session->id);
// Stream terbuka dan sedang buffering. Tampilkan riwayat sebelum mengikuti event langsung.
$seenEventIds = [];
foreach ($client->beta->sessions->events->list($session->id)->pagingEachItem() as $event) {
$seenEventIds[$event->id] = true;
}
// Ikuti event langsung, lewati apa pun yang sudah terlihat
foreach ($stream as $event) {
if (isset($seenEventIds[$event->id])) {
continue;
}
$seenEventIds[$event->id] = true;
match (true) {
$event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsAgentMessageEvent => array_walk(
$event->content,
static fn ($block) => $block instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsTextBlock ? print($block->text) : null,
),
default => null,
};
if ($event->type === 'session.status_idle') {
break;
}
}
$stream->close(); stream = client.beta.sessions.events.stream_events(session.id)
# Stream terbuka dan melakukan buffering. Tampilkan riwayat sebelum mengikuti event langsung.
seen_event_ids = Set.new
client.beta.sessions.events.list(session.id).auto_paging_each { seen_event_ids << it.id }
# Ikuti event langsung, lewati yang sudah terlihat — Set#add? mengembalikan nil untuk duplikat
stream.each do |event|
next unless seen_event_ids.add?(event.id)
case event
when Juglow::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
event.content.each { print it.text }
when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
break
else
# abaikan tipe event lainnya
end
endMendaftar event sebelumnya
Ambil riwayat event lengkap untuk sebuah sesi:
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" ant beta:sessions:events list --session-id "$SESSION_ID" --format jsonl events = client.beta.sessions.events.list(session.id)
for event in events.data:
print(f"[{event.type}] {event.processed_at}") const events = await client.beta.sessions.events.list(session.id);
for (const event of events.data) {
console.log(`[${event.type}] ${event.processed_at}`);
} var events = await client.Beta.Sessions.Events.List(session.ID);
foreach (var sessionEvent in events.Items)
{
Console.WriteLine($"[{sessionEvent.Json.GetProperty("type").GetString()}] {sessionEvent.ProcessedAt}");
} events, err := client.Beta.Sessions.Events.List(ctx, session.ID, juglow.BetaSessionEventListParams{})
if err != nil {
panic(err)
}
for _, event := range events.Data {
fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
} var events = client.beta().sessions().events().list(session.id());
for (var event : events.data()) {
var eventJson = event._json().orElseThrow().convert(JsonNode.class);
var processedAt = eventJson.path("processed_at");
IO.println("[" + eventJson.get("type").asText() + "] "
+ (processedAt.isTextual() ? processedAt.asText() : "null"));
} $events = $client->beta->sessions->events->list($session->id);
foreach ($events->data as $event) {
$processedAt = ($event->processedAt ?? null)?->format(DATE_RFC3339) ?? 'null';
echo "[{$event->type}] {$processedAt}\n";
} events = client.beta.sessions.events.list(session.id)
events.data.each { puts "[#{it.type}] #{it.processed_at}" }Teruskan filter types untuk mengembalikan hanya jenis event tertentu:
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true&types[]=agent.tool_use&types[]=agent.tool_result" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" ant beta:sessions:events list --session-id "$SESSION_ID" \
--type agent.tool_use --type agent.tool_result \
--format jsonl events = client.beta.sessions.events.list(
session.id,
types=["agent.tool_use", "agent.tool_result"],
)
for event in events.data:
print(f"[{event.type}] {event.processed_at}") const events = await client.beta.sessions.events.list(session.id, {
types: ["agent.tool_use", "agent.tool_result"],
});
for (const event of events.data) {
console.log(`[${event.type}] ${event.processed_at}`);
} var events = await client.Beta.Sessions.Events.List(session.ID, new()
{
Types = ["agent.tool_use", "agent.tool_result"],
});
foreach (var sessionEvent in events.Items)
{
Console.WriteLine($"[{sessionEvent.Json.GetProperty("type").GetString()}] {sessionEvent.ProcessedAt}");
} events, err := client.Beta.Sessions.Events.List(ctx, session.ID, juglow.BetaSessionEventListParams{
Types: []string{"agent.tool_use", "agent.tool_result"},
})
if err != nil {
panic(err)
}
for _, event := range events.Data {
fmt.Printf("[%s] %s\n", event.Type, event.ProcessedAt)
} var events = client.beta().sessions().events().list(
session.id(),
EventListParams.builder()
.addType("agent.tool_use")
.addType("agent.tool_result")
.build());
for (var event : events.data()) {
event.agentToolUse().ifPresent(toolUse ->
IO.println("[" + toolUse.type() + "] " + toolUse.processedAt()));
event.agentToolResult().ifPresent(toolResult ->
IO.println("[" + toolResult.type() + "] " + toolResult.processedAt()));
} // Di PHP, teruskan tipe yang Anda inginkan pada EventListParams; lihat Juglow PHP SDK. events = client.beta.sessions.events.list(
session.id,
types: ["agent.tool_use", "agent.tool_result"]
)
events.data.each { puts "[#{it.type}] #{it.processed_at}" }Delta event
Secara default, teks respons agen mencapai stream sebagai event agent.message yang di-buffer, masing-masing dipancarkan hanya setelah permintaan model yang menghasilkannya selesai. "Event deltas" (delta event) memungkinkan Anda merender teks tersebut secara bertahap, sebagai pratinjau langsung, selagi model masih menghasilkannya. Pratinjau bukanlah respons: pratinjau adalah alat bantu tampilan best-effort, dan agent.message yang di-buffer selalu menjadi catatan otoritatif. Klien yang mengabaikan pratinjau tetap menerima stream yang lengkap dan benar.
Memilih ikut serta dalam pratinjau
Pratinjau bersifat opt-in per koneksi stream. Tambahkan parameter query event_deltas[] ke stream yang Anda baca, dengan mengulanginya sekali untuk setiap jenis event yang ingin Anda pratinjau. Karena [] adalah pola glob shell, beri tanda kutip pada URL setiap kali Anda menyusun permintaan di shell; contoh-contoh di sini melakukan percent-encode pada tanda kurung siku sebagai %5B%5D, yang juga berfungsi. Kedua endpoint stream menerima parameter ini: stream tingkat sesi di GET /v1/sessions/{session_id}/events/stream, dan stream milik setiap thread sesi di GET /v1/sessions/{session_id}/threads/{thread_id}/stream. Nilai yang diterima adalah agent.message dan agent.thinking; nilai lain apa pun mengembalikan error 400, begitu pula permintaan dengan lebih dari 100 nilai. Pratinjau subagen muncul di stream thread milik subagen itu sendiri.
Ketika event yang dipratinjau dimulai, stream memancarkan event_start yang membawa jenis dan id event yang akan datang:
{
"type": "event_start",
"event": {
"type": "agent.message",
"id": "sevt_01abc..."
}
}Untuk agent.message, start tersebut diikuti oleh event event_delta yang membawa teks bertahap. Setiap delta menyebutkan event yang diperluasnya di event_id dan blok konten yang diperluasnya di delta.index:
{
"type": "event_delta",
"event_id": "sevt_01abc...",
"delta": {
"type": "content_delta",
"index": 0,
"content": {
"type": "text",
"text": "Here is the summary"
}
}
}Ketika event agent.thinking dipratinjau, hanya event_start yang dipancarkan. Tidak ada event event_delta yang mengikuti, dan event agent.thinking yang di-buffer yang mengakhiri pratinjau tidak membawa konten thinking; event tersebut adalah sinyal kemajuan, bukan pembawa konten.
Tidak seperti event yang dipersistensi, event_start dan event_delta tidak memiliki id atau processed_at sendiri. Satu-satunya pengenal yang dibawanya adalah id dari event yang dipratinjaunya.
Note: Delta event menggunakan format wire yang berbeda dari Streaming pesan, dan perbedaan ini disengaja.
agent.messageyang dipratinjau mendapatkan satuevent_startyang hanya diikuti oleh eventevent_delta. Tidak ada event start atau stop per blok konten dan tidak ada event stop untuk event yang dipratinjau itu sendiri. Jenis deltanya adalahcontent_delta, bukancontent_block_delta. Kode akumulator yang ditulis untuk Messages API tidak dapat dipakai begitu saja tanpa perubahan.
Mengakumulasi dan merekonsiliasi
Setiap SDK yang mendukung delta event menyertakan helper akumulator yang menangani pembukuan index untuk Anda. Helper Go, Java, Ruby, dan C# juga mengunci pratinjau yang sedang diakumulasi berdasarkan id event; dengan helper Python, TypeScript, dan PHP Anda menyimpan map tersebut sendiri dan menggabungkan setiap delta ke entri untuk id-nya. Pola manual juga berfungsi di setiap bahasa ketika Anda memerlukan pembukuan kustom: terapkan pola tersebut pada jenis event yang dihasilkan.
Dalam pola manual, perlakukan pratinjau sebagai buffer sementara dan event yang di-buffer sebagai catatannya. Kunci buffer berdasarkan (event_id, index). Rekonsiliasi per permintaan model: sebuah giliran dibuka dengan satu event session.status_running, lalu pada giliran yang selesai secara normal setiap permintaan model menghasilkan, secara berurutan, span.model_request_start, event_start, event-event event_delta, agent.message yang di-buffer, dan terakhir span.model_request_end (di tab Span events). Di wire, inilah bagian yang dipratinjau dari urutan tersebut, berselang-seling dengan event ter-buffer lain milik koneksi:
event_start {"event": {"type": "agent.message", "id": "sevt_01abc..."}}
event_delta {"event_id": "sevt_01abc...", "delta": {"type": "content_delta", "index": 0, "content": {"type": "text", "text": "..."}}}
...
agent.message {"id": "sevt_01abc...", "content": [...]}Baris event_delta berulang sekali per fragmen teks. Proses setiap event saat tiba:
- Pada
event_start, catatidyang diumumkan. Pengenalnya selalu selaras:event_start.event.id, setiapevent_delta.event_id, danidmilikagent.messageyang di-buffer adalah nilai yang sama.
- Pada setiap
event_delta, tambahkandelta.content.textke entri di(event_id, delta.index)dan render teks yang sedang berjalan. Delta pertama untuk suatuindexmembuat entri tersebut.
- Ketika
agent.messageyang di-buffer tiba, cocokkan berdasarkanid, buang pratinjau yang terakumulasi, dan render konten pesan tersebut sebagai gantinya.
- Pada
span.model_request_end, tutup pratinjau apa pun yang belum direkonsiliasi oleh event ter-buffer-nya. Tidak ada lagi delta yang akan datang untuknya. Jika giliran mengalami error atau diinterupsi, event yang di-buffer mungkin tidak pernah tiba;span.model_request_endtetap tiba.
Jaminan yang diandalkan pola ini:
- Menggabungkan delta-delta sebuah pratinjau dalam urutan kedatangan, dikunci berdasarkan
(event_id, index), menghasilkan prefiks daricontent[index].textdi event yang di-buffer (sebuah prefiks, belum tentu seluruh teks, karena delta mungkin dibuang saat beban tinggi).
- Sebuah koneksi memancarkan paling banyak satu
event_startperevent_id, dan event yang di-buffer adalah hal terakhir yang dikirimkan koneksi tersebut untukiditu.
# Aktifkan pratinjau agent.message melalui event_deltas, lalu akumulasikan secara manual.
exec {stream}< <(
curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events/stream?beta=true&event_deltas%5B%5D=agent.message" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "accept: text/event-stream"
)
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- >/dev/null <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [{"type": "text", "text": "In one short sentence, describe what an event delta is."}]
}
]
}
EOF
# Akumulasikan delta dengan kunci (id pesan, indeks konten); agent.message
# final membawa teks lengkap, sehingga menggantikan semua pratinjau untuk id tersebut.
declare -A preview
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
case $(jq -r '.type' <<<"$event_json") in
event_start)
preview_id=$(jq -r '.event.id' <<<"$event_json")
printf '[event_start id=%s]\n' "$preview_id"
;;
event_delta)
preview_key=$(jq -r '.event_id + ":" + (.delta.index | tostring)' <<<"$event_json")
preview[$preview_key]+=$(jq -r '.delta.content.text' <<<"$event_json")
printf '[event_delta] %s\n' "${preview[$preview_key]}"
;;
agent.message)
msg_id=$(jq -r '.id' <<<"$event_json")
for preview_key in "${!preview[@]}"; do
[[ $preview_key == "$msg_id":* ]] && unset "preview[$preview_key]"
done
printf '[agent.message id=%s] ' "$msg_id"
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
span.model_request_end)
for preview_key in "${!preview[@]}"; do
printf '[closing unreconciled preview for %s]\n' "${preview_key%%:*}"
done
preview=()
;;
session.status_idle)
break
;;
esac
done
exec {stream}<&- # Alur kerja ini tidak cocok dijadikan perintah shell sekali jalan.
# Gunakan salah satu contoh SDK dalam grup kode ini sebagai gantinya. # Snapshot pratinjau, dengan kunci id event. accumulate_managed_agents_event melipat setiap
# event_start / event_delta menjadi snapshot agent.message; agent.message
# yang di-buffer akan menggantikannya.
previews: dict[str, BetaManagedAgentsAgentMessageEvent] = {}
# Aktifkan pratinjau agent.message pada koneksi ini
with client.beta.sessions.events.stream(
session.id, event_deltas=["agent.message"]
) as stream:
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [{"type": "text", "text": "Describe the repo in one sentence."}],
},
],
)
for event in stream:
match event.type:
case "event_start":
snapshot = accumulate_managed_agents_event(None, event)
if snapshot is not None:
previews[event.event.id] = snapshot
print(f"event_start {event.event.type} {event.event.id}")
case "event_delta":
preview = accumulate_managed_agents_event(previews.get(event.event_id), event)
if preview is not None:
previews[event.event_id] = preview
text = "".join(block.text for block in preview.content)
print(f"event_delta preview: {text!r}")
case "agent.message":
# Event yang di-buffer adalah catatan resminya: ia menggantikan dan menutup pratinjau
preview = accumulate_managed_agents_event(previews.pop(event.id, None), event)
text = "".join(block.text for block in preview.content)
print(f"agent.message {event.id} {text!r}")
case "span.model_request_end":
# Tidak ada delta lagi yang akan datang. Tutup setiap pratinjau yang
# event buffer-nya tidak pernah tiba.
for event_id in previews:
print(f"span.model_request_end closing preview for {event_id}")
previews.clear()
case "session.status_idle":
break // Snapshot pratinjau, dikunci berdasarkan id event. `accumulateManagedAgentsEvent`
// menggabungkan pratinjau event_start / event_delta menjadi snapshot agent.message.
const previews = new Map<string, BetaManagedAgentsAgentMessageEvent>();
// Aktifkan pratinjau agent.message hanya untuk koneksi ini
const stream = await client.beta.sessions.events.stream(session.id, {
event_deltas: ["agent.message"],
});
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "Summarize the repo README" }]
}
]
});
deltas: for await (const event of stream) {
switch (event.type) {
case "event_start": {
// 1. Catat id yang diumumkan dan buka snapshot. Delta dan
// event yang di-buffer membawa id yang sama.
const preview = accumulateManagedAgentsEvent(undefined, event);
if (preview) previews.set(event.event.id, preview);
console.log(`event_start ${event.event.type} ${event.event.id}`);
break;
}
case "event_delta": {
// 2. Gabungkan fragmen ke dalam snapshot lalu render
const preview = accumulateManagedAgentsEvent(previews.get(event.event_id), event);
if (preview) {
previews.set(event.event_id, preview);
const text = preview.content
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
console.log(`event_delta preview: ${JSON.stringify(text)}`);
}
break;
}
case "agent.message": {
// 3. Event yang di-buffer adalah catatannya: ia menggantikan dan menutup pratinjau
const message = accumulateManagedAgentsEvent(previews.get(event.id), event);
previews.delete(event.id);
const text = message.content
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
console.log(`agent.message ${event.id} ${JSON.stringify(text)}`);
break;
}
case "span.model_request_end":
// 4. Tidak ada delta lagi yang akan datang. Tutup pratinjau yang belum pernah direkonsiliasi.
for (const eventId of previews.keys()) {
console.log(`span.model_request_end closing preview for ${eventId}`);
}
previews.clear();
break;
case "session.status_idle":
break deltas;
}
}
stream.controller.abort(); // Aktifkan delta event: event agent.message dipratinjau saat diproduksi.
using var stream = await client.Beta.Sessions.Events.WithRawResponse.StreamStreaming(
session.ID,
new() { EventDeltas = [BetaManagedAgentsDeltaType.AgentMessage] }
);
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Write a haiku about event streams.",
},
],
},
],
});
// Akumulasikan fragmen pratinjau per (id event, indeks konten). Event
// agent.message ter-buffer yang menyusul membawa konten lengkap, jadi ia
// menggantikan pratinjau yang terakumulasi, bukan menambahkannya.
Dictionary<string, SortedDictionary<long, string>> previews = [];
await foreach (var streamEvent in stream.Enumerate())
{
if (streamEvent.TryPickStartEvent(out var start))
{
// Pratinjau dibuka untuk event dengan id ini. Stream ini hanya mengaktifkan
// delta agent.message; TryPick* mengembalikan false alih-alih throw,
// jadi tipe pratinjau lain (termasuk yang ditambahkan nanti) dilewati.
if (start.Event.TryPickAgentMessage(out var preview))
{
Console.WriteLine($"event_start {preview.Type.Raw()} {preview.ID}");
}
}
else if (streamEvent.TryPickDeltaEvent(out var delta))
{
// Sisipkan pada indeks baru, tambahkan pada indeks yang sudah ada
if (!previews.TryGetValue(delta.EventID, out var fragments))
{
previews[delta.EventID] = fragments = [];
}
var index = delta.Delta.Index ?? 0;
fragments[index] = fragments.GetValueOrDefault(index, "") + delta.Delta.Content.Text;
Console.WriteLine($"event_delta preview: {fragments[index]}");
}
else if (streamEvent.TryPickAgentMessageEvent(out var message))
{
// Delta bersifat best-effort: buang pratinjau dan gunakan event ter-buffer
previews.Remove(message.ID);
var text = string.Concat(message.Content.Select(block =>
block.TryPickBetaManagedAgentsTextBlock(out var textBlock) ? textBlock.Text : ""));
Console.WriteLine($"agent.message {message.ID} {text}");
}
else if (streamEvent.TryPickSpanModelRequestEndEvent(out _))
{
// Tidak ada delta lagi; tutup pratinjau yang belum pernah direkonsiliasi.
foreach (var eventId in previews.Keys)
{
Console.WriteLine($"span.model_request_end closing preview for {eventId}");
}
previews.Clear();
}
else if (streamEvent.TryPickSessionStatusIdleEvent(out _))
{
break;
}
} // Aktifkan pratinjau inkremental untuk event agent.message
stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{
EventDeltas: []juglow.BetaManagedAgentsDeltaType{
juglow.BetaManagedAgentsDeltaTypeAgentMessage,
},
})
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: "Write a haiku about the ocean.",
},
}},
},
}},
}); err != nil {
panic(err)
}
// Akumulator menggabungkan fragmen event_start / event_delta menjadi
// snapshot agent.message per-event-id. Nilai nol (zero value) siap digunakan.
var previews juglow.BetaManagedAgentsEventAccumulator
deltas:
for stream.Next() {
event := stream.Current()
previews.Accumulate(event)
switch event := event.AsAny().(type) {
case juglow.BetaManagedAgentsStartEvent:
fmt.Printf("event_start %s %s\n", event.Event.Type, event.Event.ID)
case juglow.BetaManagedAgentsDeltaEvent:
fmt.Printf("event_delta preview: %q\n", previews.AgentMessageText(event.EventID))
case juglow.BetaManagedAgentsAgentMessageEvent:
// Event yang di-buffer membawa konten lengkap: akumulator
// mengganti pratinjau dengannya
fmt.Printf("agent.message %s %q\n", event.ID, previews.AgentMessageText(event.ID))
case juglow.BetaManagedAgentsSpanModelRequestEndEvent:
// Tidak ada delta lagi untuk permintaan ini. Akumulator
// membuang snapshot-nya di sini, menutup pratinjau yang tidak pernah
// direkonsiliasi oleh agent.message yang di-buffer.
fmt.Println("span.model_request_end no more deltas for this request")
case juglow.BetaManagedAgentsSessionStatusIdleEvent:
break deltas
}
}
if err := stream.Err(); err != nil {
panic(err)
}
stream.Close() // Teks pratinjau, dikunci oleh ID event lalu indeks konten. agent.message yang di-buffer menggantikannya.
Map<String, Map<Long, StringBuilder>> previews = new HashMap<>();
// Aktifkan pratinjau agent.message pada koneksi ini
try (var stream = client.beta().sessions().events().streamStreaming(
session.id(),
EventStreamParams.builder()
.addEventDelta(BetaManagedAgentsDeltaType.AGENT_MESSAGE)
.build()
)) {
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Describe the repo in one sentence.")
.build())
.build()
);
Iterable<BetaManagedAgentsStreamSessionEvents> events = stream.stream()::iterator;
deltas:
for (var event : events) {
switch (event.type().value()) {
case EVENT_START -> {
if (event.asEventStart().event().isAgentMessage()) {
var preview = event.asEventStart().event().asAgentMessage();
IO.println("event_start " + preview.type().asString() + " " + preview.id());
}
}
case EVENT_DELTA -> {
var eventDelta = event.asEventDelta();
var fragment = eventDelta.delta();
var buffer = previews
.computeIfAbsent(eventDelta.eventId(), _ -> new HashMap<>())
.computeIfAbsent(fragment.index().orElse(0L), _ -> new StringBuilder());
buffer.append(fragment.content().text());
IO.println("event_delta preview: " + buffer);
}
case AGENT_MESSAGE -> {
// Event yang di-buffer adalah catatannya: buang pratinjaunya, render kontennya
var message = event.asAgentMessage();
previews.remove(message.id());
var text = message.content().stream()
.flatMap(block -> block.text().stream())
.map(textBlock -> textBlock.text())
.collect(Collectors.joining());
IO.println("agent.message " + message.id() + " " + text);
}
case SPAN_MODEL_REQUEST_END -> {
// Tidak ada delta lagi yang akan datang. Tutup pratinjau yang event buffer-nya tidak pernah tiba.
previews.keySet().forEach(eventId ->
IO.println("span.model_request_end closing preview for " + eventId));
previews.clear();
}
case SESSION_STATUS_IDLE -> {
break deltas;
}
}
}
} // Di PHP, atur eventDeltas pada EventStreamParams dan akumulasikan dengan Juglow\Lib\Sessions\EventAccumulator. # Aktifkan delta event: pratinjau agent.message di-stream sebagai fragmen inkremental.
stream = client.beta.sessions.events.stream_events(
session.id,
event_deltas: [Juglow::Beta::BetaManagedAgentsDeltaType::AGENT_MESSAGE]
)
client.beta.sessions.events.send_(
session.id,
events: [{
type: "user.message",
content: [{type: "text", text: "Give a one-sentence project tagline."}]
}]
)
# Akumulasikan fragmen pratinjau berdasarkan (event_id, index) ke dalam buffer yang
# secara eksplisit mutable (`+""`) agar `<<` bisa menambahkan di tempat. agent.message ter-buffer dengan
# id yang sama bersifat otoritatif dan menggantikan apa pun yang dibangun oleh delta.
buffers = Hash.new do |by_event, event_id|
by_event[event_id] = Hash.new { |fragments, index| fragments[index] = +"" }
end
stream.each do |event|
case event
when Juglow::Beta::BetaManagedAgentsStartEvent
puts "event_start #{event.event.type} #{event.event.id}"
when Juglow::Beta::BetaManagedAgentsDeltaEvent
delta = event.delta
fragment = delta.content.text
buffers[event.event_id][delta.index || 0] << fragment
puts "event_delta preview: #{buffers[event.event_id][delta.index || 0].inspect}"
when Juglow::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
# Ganti: buang pratinjau yang terakumulasi dan render event lengkapnya.
buffers.delete(event.id)
puts "agent.message #{event.id} #{event.content.map(&:text).join.inspect}"
when Juglow::Beta::Sessions::BetaManagedAgentsSpanModelRequestEndEvent
# Tidak ada delta lagi yang akan datang. Tutup pratinjau yang belum pernah direkonsiliasi.
buffers.each_key { |event_id| puts "span.model_request_end closing preview for #{event_id}" }
buffers.clear
when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
break
else
# abaikan tipe event lainnya
end
endMempratinjau event thread sesi
Dalam sesi multiagen, setiap thread sesi memiliki aliran event sendiri di GET /v1/sessions/{session_id}/threads/{thread_id}/stream, dan menerima parameter event_deltas[] yang sama dengan nilai yang sama. Pratinjau dibatasi per thread secara desain: sebuah koneksi hanya mempratinjau thread yang sedang dibacanya. Pratinjau thread anak dikirimkan di stream milik anak itu sendiri dan tidak pernah diposting silang ke stream tingkat sesi, yang pratinjaunya tetap terbatas pada thread utama. Untuk melihat teks subagen saat model menghasilkannya, buka stream thread subagen tersebut.
Path stream thread mudah keliru: path-nya adalah /threads/{thread_id}/stream, bukan /events/stream (yang hanya ada di tingkat sesi), dan tidak ada endpoint /threads/{thread_id}/events/stream.
Event pratinjaunya sendiri tidak berubah. event_start dan event_delta memiliki bentuk yang sama di stream thread seperti di stream tingkat sesi, dan pola mengakumulasi dan merekonsiliasi berlaku sebagaimana tertulis. Satu-satunya penyesuaian adalah pembukuan: jalankan satu instance akumulator per koneksi stream.
# Tampilkan daftar thread sesi dan pilih satu anak: thread anak memiliki
# parent_thread_id yang tidak null, dan parent_thread_id thread utama bernilai null.
THREAD_ID=$(
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/threads?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" |
jq -er 'first(.data[] | select(.parent_thread_id != null)).id'
)
# Stream thread anak menerima parameter event_deltas[] yang sama dengan
# stream sesi. Lakukan percent-encode pada tanda kurung (%5B%5D) dan beri tanda kutip pada URL.
exec {stream}< <(
curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/threads/$THREAD_ID/stream?beta=true&event_deltas%5B%5D=agent.message" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "accept: text/event-stream"
)
while IFS= read -r -u "$stream" event_line; do
[[ $event_line == data:* ]] || continue
event_json=${event_line#data: }
case $(jq -r '.type' <<<"$event_json") in
event_delta)
jq -j '.delta.content.text' <<<"$event_json"
;;
agent.message)
# Event yang di-buffer adalah catatan otoritatif; render kontennya.
printf '\n'
jq -j '.content[] | select(.type == "text") | .text' <<<"$event_json"
printf '\n'
;;
session.thread_status_idle)
break
;;
esac
done
exec {stream}<&- # Daftarkan thread sesi dan pilih satu anak: thread anak membawa
# parent_thread_id non-null, dan parent_thread_id thread utama bernilai null
# (kueri #(parent_thread_id!=~null) milik --transform cocok dengan nilai non-null).
THREAD_ID=$(ant beta:sessions:threads list \
--session-id "$SESSION_ID" \
--format raw --transform 'data.#(parent_thread_id!=~null).id' --raw-output)
# Stream thread anak menerima parameter event_deltas yang sama dengan
# stream sesi, satu flag --event-delta per jenis event untuk dipratinjau. @tostr
# mengodekan ulang tiap field teks sebagai string JSON, sehingga setiap nilai tetap di satu
# baris YAML dan fromjson milik jq memulihkan teks aslinya.
transform='{type,frag:delta.content.text|@tostr,text:content.#(type=="text").text|@tostr}'
exec {stream}< <(ant beta:sessions:threads:events stream \
--session-id "$SESSION_ID" \
--thread-id "$THREAD_ID" \
--event-delta agent.message \
--transform "$transform" \
--format yaml)
type=
while IFS= read -r -u "$stream" line; do
case "$line" in
type:\ session.thread_status_idle) break ;;
type:\ *) type=${line#type: } ;;
frag:*)
[[ $type == event_delta ]] || continue
jq -j fromjson <<<"${line#frag: }" ;;
text:*)
[[ $type == agent.message ]] || continue
# Event yang di-buffer adalah catatan otoritatif; render kontennya.
printf '\n'
jq -r fromjson <<<"${line#text: }" ;;
esac
done
exec {stream}<&- # Daftar thread milik sesi dan pilih satu anak: thread anak membawa parent_thread_id
# non-null, sedangkan parent_thread_id thread utama bernilai null.
child_thread = next(
thread
for thread in client.beta.sessions.threads.list(session.id)
if thread.parent_thread_id is not None
)
# Stream thread anak menerima parameter event_deltas yang sama dengan
# stream sesi.
with client.beta.sessions.threads.events.stream(
child_thread.id,
session_id=session.id,
event_deltas=["agent.message"],
) as stream:
for event in stream:
match event.type:
case "event_delta":
print(event.delta.content.text, end="")
case "agent.message":
# Event yang di-buffer adalah catatan otoritatif; render kontennya
print()
for block in event.content:
if block.type == "text":
print(block.text, end="")
print()
case "session.thread_status_idle":
break // Tampilkan daftar thread sesi dan pilih anak: thread anak membawa parent_thread_id
// non-null, dan parent_thread_id thread utama bernilai null.
let childThreadId: string | undefined;
for await (const thread of client.beta.sessions.threads.list(session.id)) {
if (thread.parent_thread_id !== null) {
childThreadId = thread.id;
break;
}
}
if (!childThreadId) throw new Error("No child thread found");
// Stream thread anak menerima parameter event_deltas yang sama dengan
// stream sesi.
const stream = await client.beta.sessions.threads.events.stream(childThreadId, {
session_id: session.id,
event_deltas: ["agent.message"],
});
threadDeltas: for await (const event of stream) {
switch (event.type) {
case "event_delta":
process.stdout.write(event.delta.content.text);
break;
case "agent.message": {
// Event yang di-buffer adalah catatan otoritatif; render kontennya.
process.stdout.write("\n");
const text = event.content
.map((block) => (block.type === "text" ? block.text : ""))
.join("");
console.log(text);
break;
}
case "session.thread_status_idle":
break threadDeltas;
}
}
stream.controller.abort(); // Daftar thread sesi dan pilih satu anak: thread anak memiliki
// parent_thread_id non-null, dan parent_thread_id thread utama adalah null.
var threads = await client.Beta.Sessions.Threads.List(session.ID);
var childThread = threads.Items.First(thread => thread.ParentThreadID is not null);
// Stream thread anak menerima parameter event_deltas yang sama seperti
// stream sesi.
using var stream = await client.Beta.Sessions.Threads.Events.WithRawResponse.StreamStreaming(
childThread.ID,
new() { SessionID = session.ID, EventDeltas = [BetaManagedAgentsDeltaType.AgentMessage] }
);
await foreach (var streamEvent in stream.Enumerate())
{
if (streamEvent.TryPickDeltaEvent(out var delta))
{
Console.Write(delta.Delta.Content.Text);
}
else if (streamEvent.TryPickAgentMessageEvent(out var message))
{
// Event ter-buffer adalah catatan otoritatif; render kontennya.
Console.WriteLine();
var text = string.Concat(message.Content.Select(block =>
block.TryPickBetaManagedAgentsTextBlock(out var textBlock) ? textBlock.Text : ""));
Console.WriteLine(text);
}
else if (streamEvent.TryPickSessionThreadStatusIdleEvent(out _))
{
break;
}
} // Daftar thread sesi dan pilih satu anak: thread anak memiliki
// parent_thread_id non-null, dan parent_thread_id thread utama adalah null.
var childThreadID string
threads := client.Beta.Sessions.Threads.ListAutoPaging(ctx, session.ID, juglow.BetaSessionThreadListParams{})
for threads.Next() {
if thread := threads.Current(); thread.ParentThreadID != "" {
childThreadID = thread.ID
break
}
}
if err := threads.Err(); err != nil {
panic(err)
}
// Stream thread anak menerima parameter event_deltas yang sama seperti
// stream sesi; jalankan satu loop baca per koneksi stream.
stream := client.Beta.Sessions.Threads.Events.StreamEvents(ctx, childThreadID, juglow.BetaSessionThreadEventStreamParams{
SessionID: session.ID,
EventDeltas: []juglow.BetaManagedAgentsDeltaType{
juglow.BetaManagedAgentsDeltaTypeAgentMessage,
},
})
threadDeltas:
for stream.Next() {
switch event := stream.Current().AsAny().(type) {
case juglow.BetaManagedAgentsDeltaEvent:
fmt.Print(event.Delta.Content.Text)
case juglow.BetaManagedAgentsAgentMessageEvent:
// Event yang di-buffer adalah catatan otoritatif; render kontennya.
fmt.Println()
// daftar bertipe konkret: BetaManagedAgentsTextBlock
for _, block := range event.Content {
fmt.Print(block.Text)
}
fmt.Println()
case juglow.BetaManagedAgentsSessionThreadStatusIdleEvent:
break threadDeltas
}
}
if err := stream.Err(); err != nil {
panic(err)
}
stream.Close() // Daftar thread sesi dan pilih satu child: child thread membawa parent_thread_id
// yang non-null, dan parent_thread_id thread utama bernilai null.
var childThread = client.beta().sessions().threads().list(session.id()).autoPager().stream()
.filter(thread -> thread.parentThreadId().isPresent())
.findFirst()
.orElseThrow();
// Stream child thread menerima parameter event_deltas yang sama dengan stream sesi.
// Kelas params-nya berbagi nama sederhana dengan yang level sesi, jadi kualifikasikan.
try (var stream = client.beta().sessions().threads().events().streamStreaming(
childThread.id(),
com.juglow.models.beta.sessions.threads.events.EventStreamParams.builder()
.sessionId(session.id())
.addEventDelta(BetaManagedAgentsDeltaType.AGENT_MESSAGE)
.build()
)) {
Iterable<BetaManagedAgentsStreamSessionThreadEvents> events = stream.stream()::iterator;
threadDeltas:
for (var event : events) {
switch (event.type().value()) {
case EVENT_DELTA -> IO.print(event.asEventDelta().delta().content().text());
case AGENT_MESSAGE -> {
// Event yang di-buffer adalah catatan otoritatif; render kontennya.
IO.println();
event.asAgentMessage().content().forEach(block -> block.text().ifPresent(textBlock -> IO.print(textBlock.text())));
IO.println();
}
case SESSION_THREAD_STATUS_IDLE -> {
break threadDeltas;
}
}
}
} // Di PHP, atur eventDeltas pada EventStreamParams thread dan akumulasikan dengan Juglow\Lib\Sessions\EventAccumulator. # Daftarkan thread milik sesi dan pilih satu anak: thread anak memiliki
# parent_thread_id non-null, dan parent_thread_id thread utama bernilai null.
child_thread = client.beta.sessions.threads.list(session.id).to_enum.find { it.parent_thread_id }
# Stream thread anak menerima parameter event_deltas yang sama dengan
# stream sesi.
stream = client.beta.sessions.threads.events.stream_events(
child_thread.id,
session_id: session.id,
event_deltas: [Juglow::Beta::BetaManagedAgentsDeltaType::AGENT_MESSAGE]
)
stream.each do |event|
case event
when Juglow::Beta::BetaManagedAgentsDeltaEvent
print event.delta.content.text
when Juglow::Beta::Sessions::BetaManagedAgentsAgentMessageEvent
# Event yang ter-buffer adalah catatan otoritatif; render kontennya.
puts
event.content.each { print it.text }
puts
when Juglow::Beta::Sessions::BetaManagedAgentsSessionThreadStatusIdleEvent
break
else
# abaikan tipe event lainnya
end
endLoop pembacaan keluar pada session.thread_status_idle, event yang dipancarkan ketika giliran thread sesi selesai dan thread menjadi idle.
Keterbatasan
Pratinjau disetel untuk responsivitas. Bangun dengan memperhatikan batasan berikut:
- Best effort: Saat beban tinggi, server mungkin membuang delta untuk suatu event. Ketika itu terjadi, Anda menerima prefiks teks yang bersambung lalu tidak ada delta lebih lanjut untuk event tersebut.
agent.messageyang di-buffer tetap tiba secara lengkap. Jangan pernah memperlakukan pratinjau yang terakumulasi sebagai final.
- Tidak ada replay saat menyambung kembali: Delta hanya dikirimkan ke koneksi yang memilih ikut serta, selama koneksi itu terbuka. Ini berlaku sama untuk stream tingkat sesi maupun setiap stream thread sesi, dan koneksi yang dibuka setelah permintaan model dimulai tidak menerima delta untuk event yang sedang berlangsung tersebut. Jika stream terputus, ikuti prosedur penyambungan kembali di tab Streaming event: buka kembali stream dan daftarkan riwayat event. Riwayat tersebut mencakup event ter-buffer apa pun yang dipancarkan selagi Anda terputus, termasuk
agent.messageyang ditunggu pratinjau Anda. Tidak ada cara untuk meminta ulang delta yang terlewat.
- Satu thread, hanya teks: Pratinjau mencakup teks asisten pada thread yang sedang dibaca koneksi. Penggunaan alat, hasil alat, hasil MCP, dan aktivitas pada thread sesi lain mana pun tidak pernah dipratinjau pada koneksi tersebut.
agent.thinkinghanya start: Pratinjauagent.thinkinghanya memancarkanevent_startsebagai sinyal bahwa blok thinking telah dimulai; tidak ada eventevent_deltayang mengikutinya.
- Tidak pernah dipersistensi:
event_startdanevent_deltahanya ada di stream langsung. Keduanya tidak muncul di riwayat event sesi (GET /v1/sessions/{session_id}/events) atau di riwayat event thread sesi mana pun.
Memecahkan masalah pratinjau
Jika stream tidak berperilaku seperti yang Anda harapkan:
| Yang Anda lihat | Artinya |
|---|---|
Stream dengan event ter-buffer tetapi tanpa event_start atau event_delta | Koneksi yang Anda baca tidak memilih ikut serta (event_deltas[] berlaku per koneksi, bukan per sesi), atau giliran tersebut tidak pernah menyentuh thread yang Anda stream. Pratinjau dibatasi per thread, jadi daftarkan thread sesi (GET /v1/sessions/{session_id}/threads) untuk menemukan thread mana yang berjalan. |
| 404 pada URL stream | Path atau salah satu ID salah, atau permintaan tidak membawa header beta managed-agents sama sekali. Endpoint thread dibatasi beta, jadi tanpa header tersebut endpoint itu tidak ada. |
400 yang menyebut event_deltas | Hanya agent.message dan agent.thinking yang diterima. |
Skenario tambahan
Menangani pemanggilan alat kustom
Ketika agen memanggil alat kustom:
- Sesi memancarkan event
agent.custom_tool_useyang berisi nama alat dan input.
- Sesi berhenti sejenak dengan event
session.status_idleyang berisistop_reason: requires_action. ID event yang memblokir ada di arraystop_reason.event_ids.
- Jalankan alat di sistem Anda dan kirim event
user.custom_tool_resultuntuk masing-masing, dengan meneruskan ID event di parametercustom_tool_use_idbersama konten hasilnya.
- Setelah semua event yang memblokir terselesaikan, sesi bertransisi kembali ke
running.
exec {stream_fd}< <(curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "accept: text/event-stream")
while IFS= read -r -u "$stream_fd" line; do
[[ $line == data:* ]] || continue
event_json="${line#data: }"
stop_reason=$(jq -r 'select(.type == "session.status_idle") | .stop_reason.type // empty' <<<"$event_json")
case "$stop_reason" in
requires_action)
while IFS= read -r event_id; do
# Jalankan alat dan kirim hasilnya kembali
result=$(call_tool "$event_id")
jq -n --arg id "$event_id" --arg result "$result" \
'{events: [{type: "user.custom_tool_result", custom_tool_use_id: $id, content: [{type: "text", text: $result}]}]}' |
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @-
done < <(jq -r '.stop_reason.event_ids[]' <<<"$event_json")
;;
end_turn)
break
;;
esac
done
exec {stream_fd}<&- # Alur kerja ini tidak cocok diterjemahkan ke perintah shell sekali jalan.
# Gunakan salah satu contoh SDK dalam grup kode ini sebagai gantinya. with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop_reason := event.stop_reason):
match stop_reason.type:
case "requires_action":
for event_id in stop_reason.event_ids:
# Cari event custom tool use dan jalankan
tool_event = events_by_id[event_id]
result = call_tool(tool_event.name, tool_event.input)
# Kirim hasilnya kembali
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.custom_tool_result",
"custom_tool_use_id": event_id,
"content": [{"type": "text", "text": result}],
},
],
)
case "end_turn":
break const stream = await client.beta.sessions.events.stream(session.id);
for await (const event of stream) {
if (event.type !== "session.status_idle") continue;
if (event.stop_reason.type === "end_turn") break;
if (event.stop_reason.type !== "requires_action") continue;
for (const eventId of event.stop_reason.event_ids) {
// Cari event custom tool use dan jalankan
const toolEvent = eventsById.get(eventId);
if (!toolEvent) continue;
const result = await callTool(toolEvent.name, toolEvent.input);
// Kirim hasilnya kembali
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.custom_tool_result",
custom_tool_use_id: eventId,
content: [{ type: "text", text: result }],
},
],
});
}
} await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
{
if (streamEvent.Value is not BetaManagedAgentsSessionStatusIdleEvent idle) continue;
if (idle.StopReason?.Value is BetaManagedAgentsSessionRequiresAction requiresAction)
{
foreach (var eventId in requiresAction.EventIds)
{
// Cari event penggunaan alat kustom dan jalankan
var toolEvent = eventsById[eventId];
var result = await CallTool(toolEvent.Name, toolEvent.Input);
// Kirim hasilnya kembali
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserCustomToolResultEventParams
{
Type = BetaManagedAgentsUserCustomToolResultEventParamsType.UserCustomToolResult,
CustomToolUseID = eventId,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = result,
},
],
},
],
});
}
}
else if (idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
{
break;
}
} stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{})
defer stream.Close()
loop:
for stream.Next() {
event, ok := stream.Current().AsAny().(juglow.BetaManagedAgentsSessionStatusIdleEvent)
if !ok {
continue
}
switch stopReason := event.StopReason.AsAny().(type) {
case juglow.BetaManagedAgentsSessionRequiresAction:
for _, eventID := range stopReason.EventIDs {
// Cari event custom tool use dan jalankan
toolEvent := eventsByID[eventID]
result := callTool(toolEvent.Name, toolEvent.Input)
// Kirim hasilnya kembali
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserCustomToolResult: &juglow.BetaManagedAgentsUserCustomToolResultEventParams{
Type: juglow.BetaManagedAgentsUserCustomToolResultEventParamsTypeUserCustomToolResult,
CustomToolUseID: eventID,
Content: []juglow.BetaManagedAgentsUserCustomToolResultEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: result,
},
}},
},
}},
}); err != nil {
panic(err)
}
}
case juglow.BetaManagedAgentsSessionEndTurn:
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
} try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
stream.stream()
.filter(BetaManagedAgentsStreamSessionEvents::isSessionStatusIdle)
.map(idleEvent -> idleEvent.asSessionStatusIdle().stopReason())
.takeWhile(stopReason -> !stopReason.isEndTurn())
.filter(stopReason -> stopReason.isRequiresAction())
.flatMap(stopReason -> stopReason.asRequiresAction().eventIds().stream())
.forEach(eventId -> {
// Cari event custom tool use dan jalankan
var toolEvent = eventsById.get(eventId);
var result = callTool(toolEvent.name(), toolEvent.input());
// Kirim hasilnya kembali
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserCustomToolResultEventParams.builder()
.type(BetaManagedAgentsUserCustomToolResultEventParams.Type.USER_CUSTOM_TOOL_RESULT)
.customToolUseId(eventId)
.addTextContent(result)
.build())
.build());
});
} $stream = $client->beta->sessions->events->streamStream($session->id);
foreach ($stream as $event) {
if ($event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent && $event->stopReason) {
switch (true) {
case $event->stopReason instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionRequiresAction:
foreach ($event->stopReason->eventIDs as $eventId) {
// Cari event penggunaan alat kustom lalu jalankan
$toolEvent = $eventsById[$eventId];
$result = callTool($toolEvent->name, $toolEvent->input);
// Kirim hasilnya kembali
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.custom_tool_result',
'custom_tool_use_id' => $eventId,
'content' => [['type' => 'text', 'text' => $result]],
],
],
);
}
break;
case $event->stopReason instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionEndTurn:
break 2;
}
}
} client.beta.sessions.events.stream_events(session.id).each do |event|
case event
when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
stop_reason = event.stop_reason
case stop_reason
when Juglow::Beta::Sessions::BetaManagedAgentsSessionRequiresAction
stop_reason.event_ids.each do |event_id|
# Cari event penggunaan custom tool lalu jalankan
tool_event = events_by_id[event_id]
result = call_tool.call(tool_event.name, tool_event.input)
# Kirim hasilnya kembali
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.custom_tool_result",
custom_tool_use_id: event_id,
content: [{type: "text", text: result}]
}
]
)
end
when Juglow::Beta::Sessions::BetaManagedAgentsSessionEndTurn
break
end
end
endKonfirmasi alat
Panggilan alat menunggu konfirmasi Anda di bawah kebijakan izin always_ask, atau di bawah auto saat server tidak mencapai keputusan. Saat hal itu terjadi:
- Sesi memancarkan event
agent.tool_useatauagent.mcp_tool_use.
- Sesi dijeda dengan event
session.status_idleyangstop_reason.type-nya adalahrequires_action. ID event yang memblokir ada di arraystop_reason.event_ids.
- Kirim event
user.tool_confirmationuntuk masing-masing, dengan meneruskan ID event di parametertool_use_id. Aturresultke"allow"atau"deny". Gunakandeny_messageuntuk menjelaskan penolakan.
- Setelah semua event yang memblokir diselesaikan, sesi kembali beralih ke
running.
Setiap event agent.tool_use dan agent.mcp_tool_use membawa evaluated_permission (allow, ask, atau deny), dan hanya event yang evaluated_permission-nya "ask" yang menunggu konfirmasi. Sebagian besar event juga membawa objek evaluation yang mencatat kebijakan mana yang menghasilkan keputusan tersebut, seperti dijelaskan di Melihat bagaimana setiap panggilan dievaluasi. Misalnya, panggilan bash yang dijeda di bawah kebijakan always_ask muncul di stream sebagai berikut:
{
"type": "agent.tool_use",
"id": "sevt_01def...",
"name": "bash",
"input": {
"command": "pip install -r requirements.txt"
},
"evaluated_permission": "ask",
"evaluation": {
"type": "always_ask"
},
"processed_at": "2026-03-25T14:01:45Z"
} exec {stream_fd}< <(curl --fail-with-body -sS -N \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events/stream?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-H "accept: text/event-stream")
while IFS= read -r -u "$stream_fd" line; do
[[ $line == data:* ]] || continue
event_json="${line#data: }"
stop_reason=$(jq -r 'select(.type == "session.status_idle") | .stop_reason.type // empty' <<<"$event_json")
case "$stop_reason" in
requires_action)
while IFS= read -r event_id; do
# Setujui panggilan alat yang tertunda
jq -n --arg id "$event_id" \
'{events: [{type: "user.tool_confirmation", tool_use_id: $id, result: "allow"}]}' |
curl --fail-with-body -sS \
"https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @-
done < <(jq -r '.stop_reason.event_ids[]' <<<"$event_json")
;;
end_turn)
break
;;
esac
done
exec {stream_fd}<&- # Alur kerja ini tidak cocok diterjemahkan ke perintah shell sekali jalan.
# Gunakan salah satu contoh SDK dalam grup kode ini sebagai gantinya. with client.beta.sessions.events.stream(session.id) as stream:
for event in stream:
if event.type == "session.status_idle" and (stop_reason := event.stop_reason):
match stop_reason.type:
case "requires_action":
for event_id in stop_reason.event_ids:
# Setujui panggilan alat yang tertunda
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.tool_confirmation",
"tool_use_id": event_id,
"result": "allow",
},
],
)
case "end_turn":
break const stream = await client.beta.sessions.events.stream(session.id);
for await (const event of stream) {
if (event.type !== "session.status_idle") continue;
if (event.stop_reason.type === "end_turn") break;
if (event.stop_reason.type !== "requires_action") continue;
for (const eventId of event.stop_reason.event_ids) {
// Setujui panggilan alat yang tertunda
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.tool_confirmation",
tool_use_id: eventId,
result: "allow",
},
],
});
}
} await foreach (var streamEvent in client.Beta.Sessions.Events.StreamStreaming(session.ID))
{
if (streamEvent.Value is not BetaManagedAgentsSessionStatusIdleEvent idle) continue;
if (idle.StopReason?.Value is BetaManagedAgentsSessionRequiresAction requiresAction)
{
foreach (var eventId in requiresAction.EventIds)
{
// Setujui panggilan alat yang tertunda
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserToolConfirmationEventParams
{
Type = BetaManagedAgentsUserToolConfirmationEventParamsType.UserToolConfirmation,
ToolUseID = eventId,
Result = BetaManagedAgentsUserToolConfirmationEventParamsResult.Allow,
},
],
});
}
}
else if (idle.StopReason?.Value is BetaManagedAgentsSessionEndTurn)
{
break;
}
} stream := client.Beta.Sessions.Events.StreamEvents(ctx, session.ID, juglow.BetaSessionEventStreamParams{})
defer stream.Close()
loop:
for stream.Next() {
event, ok := stream.Current().AsAny().(juglow.BetaManagedAgentsSessionStatusIdleEvent)
if !ok {
continue
}
switch stopReason := event.StopReason.AsAny().(type) {
case juglow.BetaManagedAgentsSessionRequiresAction:
for _, eventID := range stopReason.EventIDs {
// Setujui panggilan alat yang tertunda
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserToolConfirmation: &juglow.BetaManagedAgentsUserToolConfirmationEventParams{
Type: juglow.BetaManagedAgentsUserToolConfirmationEventParamsTypeUserToolConfirmation,
ToolUseID: eventID,
Result: juglow.BetaManagedAgentsUserToolConfirmationEventParamsResultAllow,
},
}},
}); err != nil {
panic(err)
}
}
case juglow.BetaManagedAgentsSessionEndTurn:
break loop
}
}
if err := stream.Err(); err != nil {
panic(err)
} try (var stream = client.beta().sessions().events().streamStreaming(session.id())) {
stream.stream()
.filter(BetaManagedAgentsStreamSessionEvents::isSessionStatusIdle)
.map(idleEvent -> idleEvent.asSessionStatusIdle().stopReason())
.takeWhile(stopReason -> !stopReason.isEndTurn())
.filter(stopReason -> stopReason.isRequiresAction())
.flatMap(stopReason -> stopReason.asRequiresAction().eventIds().stream())
// Setujui setiap panggilan alat yang tertunda
.forEach(toolUseId -> client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserToolConfirmationEventParams.builder()
.type(BetaManagedAgentsUserToolConfirmationEventParams.Type.USER_TOOL_CONFIRMATION)
.toolUseId(toolUseId)
.result(BetaManagedAgentsUserToolConfirmationEventParams.Result.ALLOW)
.build())
.build()));
} $stream = $client->beta->sessions->events->streamStream($session->id);
foreach ($stream as $event) {
if ($event instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionStatusIdleEvent && $event->stopReason) {
switch (true) {
case $event->stopReason instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionRequiresAction:
foreach ($event->stopReason->eventIDs as $eventId) {
// Setujui panggilan alat yang tertunda
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.tool_confirmation',
'tool_use_id' => $eventId,
'result' => 'allow',
],
],
);
}
break;
case $event->stopReason instanceof \Juglow\Beta\Sessions\Events\ManagedAgentsSessionEndTurn:
break 2;
}
}
} client.beta.sessions.events.stream_events(session.id).each do |event|
case event
when Juglow::Beta::Sessions::BetaManagedAgentsSessionStatusIdleEvent
stop_reason = event.stop_reason
case stop_reason
when Juglow::Beta::Sessions::BetaManagedAgentsSessionRequiresAction
stop_reason.event_ids.each do |event_id|
# Setujui panggilan alat yang tertunda
client.beta.sessions.events.send_(
session.id,
events: [
{type: "user.tool_confirmation", tool_use_id: event_id, result: "allow"}
]
)
end
when Juglow::Beta::Sessions::BetaManagedAgentsSessionEndTurn
break
end
end
endMelanjutkan sesi yang idle
Sesi bertahan di antara interaksi. Riwayat percakapan dipertahankan kecuali sesi dihapus secara eksplisit. Ketika sesi menjadi idle, sandbox-nya di-checkpoint, mempertahankan status sandbox lengkap, termasuk filesystem, paket yang terinstal, dan file apa pun yang dibuat agen. Ini memungkinkan Anda melanjutkan dengan bersih dari ketidakaktifan.
Note: Meskipun riwayat sesi dipersistensi hingga dihapus, status sandbox hanya dipertahankan selama 30 hari setelah sandbox dibuat. Aktivitas tidak memperpanjang jendela ini: setelah 30 hari status sandbox (file, alat yang terinstal, dan sebagainya) tidak dapat dipulihkan, dan sesi yang dilanjutkan dimulai dari sandbox baru. Jika alur kerja Anda bergantung pada isi sandbox, minta agen menulis artefak penting ke outputs sebelum jendela tersebut berakhir.
Untuk melanjutkan sesi, kirim event user.message ke sesi tersebut seperti biasa:
# Di produksi, berikan ID tersimpan dari sesi yang ingin Anda lanjutkan.
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{
"type": "user.message",
"content": [
{"type": "text", "text": "Now run the tests against the changes you made earlier."}
]
}
]
}
EOF # Di produksi, berikan ID tersimpan dari sesi yang ingin Anda lanjutkan.
ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: user.message
content:
- type: text
text: Now run the tests against the changes you made earlier.
YAML # Lanjutkan sesi yang dibuat sebelumnya dengan mengirimkan event user.message baru.
# Di produksi, berikan ID tersimpan dari sesi yang ingin Anda lanjutkan.
client.beta.sessions.events.send(
session.id,
events=[
{
"type": "user.message",
"content": [
{
"type": "text",
"text": "Now run the tests against the changes you made earlier.",
},
],
},
],
) // Lanjutkan sesi yang dibuat sebelumnya dengan mengirimkan event pengguna baru.
// Di produksi, berikan ID tersimpan dari sesi yang ingin Anda lanjutkan.
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [
{
type: "text",
text: "Now run the tests against the changes you made earlier.",
},
],
},
],
}); // Lanjutkan sesi yang dibuat sebelumnya berdasarkan ID. Di produksi, berikan
// ID sesi yang Anda simpan saat sesi dibuat.
await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsUserMessageEventParams
{
Type = BetaManagedAgentsUserMessageEventParamsType.UserMessage,
Content =
[
new BetaManagedAgentsTextBlock
{
Type = BetaManagedAgentsTextBlockType.Text,
Text = "Now run the tests against the changes you made earlier.",
},
],
},
],
}); // Lanjutkan sesi yang dibuat sebelumnya dengan mengirimkan event user.message
// baru. Di produksi, berikan ID tersimpan dari sesi yang akan dilanjutkan.
if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfUserMessage: &juglow.BetaManagedAgentsUserMessageEventParams{
Type: juglow.BetaManagedAgentsUserMessageEventParamsTypeUserMessage,
Content: []juglow.BetaManagedAgentsUserMessageEventParamsContentUnion{{
OfText: &juglow.BetaManagedAgentsTextBlockParam{
Type: juglow.BetaManagedAgentsTextBlockTypeText,
Text: "Now run the tests against the changes you made earlier.",
},
}},
},
}},
}); err != nil {
panic(err)
} // Lanjutkan sesi yang dibuat sebelumnya berdasarkan ID. Di produksi, teruskan
// ID sesi yang Anda simpan saat sesi dibuat.
client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsUserMessageEventParams.builder()
.type(BetaManagedAgentsUserMessageEventParams.Type.USER_MESSAGE)
.addTextContent("Now run the tests against the changes you made earlier.")
.build())
.build()); // Lanjutkan sesi yang dibuat sebelumnya dengan mengirimkan event user.message baru.
// Di produksi, berikan ID sesi yang Anda simpan saat sesi dibuat.
$client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'user.message',
'content' => [
[
'type' => 'text',
'text' => 'Now run the tests against the changes you made earlier.',
],
],
],
],
); # Melanjutkan sesi cukup dengan mengirim event berikutnya ke sesi tersebut. Di produksi,
# teruskan ID sesi yang Anda simpan saat sesi dibuat.
client.beta.sessions.events.send_(
session.id,
events: [
{
type: "user.message",
content: [
{type: "text", text: "Now run the tests against the changes you made earlier."}
]
}
]
)Mencapai anggaran sesi
Sesi yang dibuat dengan anggaran berhenti sejenak alih-alih membelanjakan berlebih. Ketika biaya daftar terlacak sesi mencapai batas, platform menghentikan sejenak setiap thread sebelum permintaan model berikutnya, dan sesi menjadi idle dengan stop_reason berupa budget_reached alih-alih dihentikan. Permintaan yang membawa total melewati batas berjalan hingga selesai, sehingga list_cost yang dilaporkan oleh snapshot session.usage dapat terbaca tepat di batas atau sedikit melewati batas. Di stream, jeda tersebut tiba sebagai tiga event, secara berurutan:
session.thread_status_idledenganstop_reason: budget_reached, untuk setiap thread saat thread tersebut berhenti sejenak.
session.usage, snapshot penggunaan kumulatif sesi dan biaya daftar terlacak.
session.status_idledenganstop_reason: budget_reached. Eventsession.usageselalu tepat mendahului idle ini.
Thread yang permintaan terakhirnya sekaligus melewati batas dan menyelesaikan gilirannya melaporkan end_turn pada event session.thread_status_idle miliknya sendiri sementara sesi tetap melaporkan budget_reached; gunakan stop_reason tingkat sesi sebagai kunci untuk mendeteksi jeda.
Selama sesi berada di batasnya, sesi hanya menerima event yang menyelesaikan pekerjaan yang sudah berlangsung: user.tool_confirmation, user.tool_result, user.custom_tool_result, dan user.interrupt. Event apa pun yang akan memulai pekerjaan baru, termasuk user.message, ditolak dengan error 400 yang menyebutkan daftar tersebut. Ketika sesi memiliki thread yang menunggu permintaan alat sekaligus thread yang dijeda di batas, stop_reason tingkat sesi adalah requires_action, bukan budget_reached: menyelesaikan permintaan tersebut tidak memicu permintaan model, jadi tanggapi seperti biasa.
Tidak ada event yang melanjutkan sesi yang dijeda di batasnya. Sebagai gantinya, perbarui anggaran sesi: mengubah batas ke nilai apa pun di atas biaya daftar yang telah terpakai, atau menghapus anggaran dengan memperbarui sesi menggunakan "budget": null, akan melanjutkan pekerjaan yang dijeda secara otomatis. Lihat Anggaran sesi untuk cara biaya daftar dilacak dan semantik pembaruan anggaran selengkapnya.
Mengirim pesan sistem
Note:
system.messagedidukung oleh Haijun Fable 5.1, Haijun Mythos 5.1, Haijun Fable 5, Haijun Mythos 5, Haijun Opus 5.5, Haijun Opus 5, dan Haijun Opus 4.8. Jika model utama agen tidak mendukung injeksi sistem di tengah percakapan, event akan ditolak dengan error validasimodel_does_not_support_mid_conversation_system. Model subagen tidak diperiksa, karenasystem.messagehanya masuk ke thread utama.
Kirim event system.message untuk memberi agen konteks tingkat sistem yang diistimewakan yang berlaku untuk giliran yang menyertainya dan semua giliran berikutnya. Tidak seperti field system pada definisi agen (yang menetapkan prompt sistem tingkat atas), konten system.message ditambahkan ke konteks sistem sesi sebagai giliran role: "system" alih-alih menggantikan prompt tersebut. Gunakan ketika agen memerlukan panduan tingkat sistem yang diperbarui di tengah sesi: persona yang berbeda, batasan yang direvisi, atau konteks yang diambil saat runtime yang seharusnya membentuk perilaku model ke depannya.
curl --fail-with-body -sS "https://haijun.my.id/v1/sessions/$SESSION_ID/events?beta=true" \
-H "x-api-key: $JUGLOW_API_KEY" \
-H "juglow-version: 2023-06-01" \
-H "juglow-beta: managed-agents-2026-04-01" \
-H "content-type: application/json" \
-d @- <<'EOF'
{
"events": [
{
"type": "system.message",
"content": [
{"type": "text", "text": "The user's current timezone is America/New_York."}
]
}
]
}
EOF ant beta:sessions:events send --session-id "$SESSION_ID" <<'YAML'
events:
- type: system.message
content:
- type: text
text: "The user's current timezone is America/New_York."
YAML client.beta.sessions.events.send(
session.id,
events=[
{
"type": "system.message",
"content": [
{
"type": "text",
"text": "The user's current timezone is America/New_York.",
},
],
},
],
) await client.beta.sessions.events.send(session.id, {
events: [
{
type: "system.message",
content: [
{
type: "text",
text: "The user's current timezone is America/New_York.",
},
],
},
],
}); await client.Beta.Sessions.Events.Send(session.ID, new()
{
Events =
[
new BetaManagedAgentsSystemMessageEventParams
{
Type = BetaManagedAgentsSystemMessageEventParamsType.SystemMessage,
Content =
[
new BetaManagedAgentsSystemContentBlock
{
Type = BetaManagedAgentsSystemContentBlockType.Text,
Text = "The user's current timezone is America/New_York.",
},
],
},
],
}); if _, err := client.Beta.Sessions.Events.Send(ctx, session.ID, juglow.BetaSessionEventSendParams{
Events: []juglow.BetaManagedAgentsEventParamsUnion{{
OfSystemMessage: &juglow.BetaManagedAgentsSystemMessageEventParams{
Type: juglow.BetaManagedAgentsSystemMessageEventParamsTypeSystemMessage,
Content: []juglow.BetaManagedAgentsSystemContentBlockParam{{
Type: juglow.BetaManagedAgentsSystemContentBlockTypeText,
Text: "The user's current timezone is America/New_York.",
}},
},
}},
}); err != nil {
panic(err)
} client.beta().sessions().events().send(
session.id(),
EventSendParams.builder()
.addEvent(BetaManagedAgentsSystemMessageEventParams.builder()
.type(BetaManagedAgentsSystemMessageEventParams.Type.SYSTEM_MESSAGE)
.addTextContent("The user's current timezone is America/New_York.")
.build())
.build()); $client->beta->sessions->events->send(
$session->id,
events: [
[
'type' => 'system.message',
'content' => [
[
'type' => 'text',
'text' => "The user's current timezone is America/New_York.",
],
],
],
],
); client.beta.sessions.events.send_(
session.id,
events: [
{
type: "system.message",
content: [
{type: "text", text: "The user's current timezone is America/New_York."}
]
}
]
)Selama sesi idle dengan stop_reason: requires_action, system.message hanya diterima ketika mengikuti event hasil alat dalam permintaan yang sama; jika dikirim sendiri atau bersama user.message, event tersebut ditolak hingga event alat yang tertunda terselesaikan. content menerima 1–1000 item teks.
Melacak penggunaan
Objek sesi menyertakan field usage dengan penggunaan kumulatif sesi: jumlah token, penggunaan alat server, waktu aktif, dan biaya daftar yang dilacak. Ambil sesi setelah sesi tersebut menjadi idle untuk membaca total terbaru.
{
"id": "sesn_01...",
"status": "idle",
"usage": {
"input_tokens": 5000,
"output_tokens": 3200,
"cache_read_input_tokens": 20000,
"cache_creation": {
"ephemeral_5m_input_tokens": 2000,
"ephemeral_1h_input_tokens": 0
},
"list_cost": {
"amount": "187",
"currency": "USD"
},
"active_seconds": 342.5,
"server_tool_use": {
"web_search_requests": 3,
"web_fetch_requests": 0
}
}
}input_tokens melaporkan token input yang tidak di-cache dan output_tokens melaporkan total token output di seluruh panggilan model dalam sesi. Field cache_read_input_tokens melaporkan token yang dibaca dari cache prompt, dan objek cache_creation merinci token pembuatan cache berdasarkan masa berlaku cache (ephemeral_5m_input_tokens dan ephemeral_1h_input_tokens). Entri cache menggunakan TTL 5 menit secara default, sehingga giliran yang berurutan dalam jendela waktu tersebut mendapat manfaat dari pembacaan cache, yang mengurangi biaya per token.
list_cost adalah konsumsi kumulatif sesi yang dihargai berdasarkan tarif daftar publik, sebagai bilangan bulat sen dalam bentuk string, dengan kode mata uang. active_seconds adalah waktu kumulatif selama sesi memiliki setidaknya satu thread yang berjalan; aktivitas yang tumpang tindih dari thread konkuren dihitung sekali, berbeda dengan active_seconds dalam objek stats sesi, yang menjumlahkan waktu aktif masing-masing thread. Angka yang telah dideduplikasi ini adalah durasi yang menjadi dasar penetapan harga biaya runtime sesi. server_tool_use menghitung permintaan alat yang dieksekusi server untuk penetapan harga: permintaan pencarian web dihargai ke dalam biaya daftar per permintaan, dan permintaan web fetch tidak dikenai biaya per permintaan dan tidak diukur, sehingga web_fetch_requests bernilai 0. usage milik setiap thread sesi juga memuat list_cost dan active_seconds. Angka per thread dibulatkan secara independen dan tidak mencakup biaya waktu berjalan sesi, sehingga jumlahnya tidak persis sama dengan list_cost sesi; angka sesi adalah angka yang otoritatif.
Anda tidak perlu melakukan polling pada sesi untuk mengamati total ini. Event session.usage membawa snapshot kumulatif yang sama (objek usage, ditambah budget sesi, yang bernilai null ketika sesi tidak memilikinya) pada stream sesi dan dalam riwayat event. Event ini dipancarkan pada transisi idle, bukan berdasarkan timer: sesi memancarkan satu event tepat sebelum menjadi idle, apa pun alasan berhentinya, dan satu event ketika sebuah thread dijeda pada anggaran sesi. Oleh karena itu, pembaca stream melihat biaya akhir dari suatu giliran, atau dari pekerjaan yang mencapai anggaran, tanpa pengambilan tambahan.
Untuk menerapkan batas pengeluaran, tetapkan anggaran sesi alih-alih melakukan polling penggunaan dan menghentikan sesi sendiri. Platform menghargai konsumsi sesi secara terus-menerus dan menjeda setiap thread sebelum permintaan model berikutnya begitu biaya daftar sesi mencapai batas; lihat Mencapai anggaran sesi untuk mengetahui tampilannya pada stream.
Observabilitas Console
Haijun Console menyertakan penampil sesi untuk memeriksa apa yang dilakukan agen tanpa menulis kode apa pun. Di sidebar Console, di bawah Managed Agents, pilih Sessions untuk melihat setiap sesi di workspace beserta status, agen, penggunaan token, biaya, dan waktu pembuatannya, lalu pilih sebuah sesi untuk membukanya. Penampil sesi hanya dapat diakses oleh Developer dan Admin. Penampil ini menampilkan:
- Minimap timeline: Ikhtisar aktivitas sesi dari waktu ke waktu yang dapat diperbesar, dengan satu jalur per thread dalam sesi multiagen. Pilih sebuah jalur untuk melihat thread tersebut, atau pilih sebuah penanda untuk melompat ke event-nya.
- Transkrip: Percakapan yang dikelompokkan berdasarkan permintaan model, termasuk pemikiran, panggilan alat beserta input dan hasilnya, serta teks pesan saat di-streaming. Anda dapat memfilter event dan menyalin atau mengunduhnya sebagai JSON.
- Inspector: Panel samping yang dapat diubah ukurannya dengan detail tentang sesi, dalam lima tab:
- Session menampilkan detail dan metadata sesi, biaya kumulatifnya dari waktu ke waktu, dan pengeluaran terhadap anggaran sesi jika ditetapkan.
- Events mencantumkan setiap event mentah pada thread saat ini sesuai urutan pengirimannya oleh server; pilih sebuah event untuk melihat JSON-nya. Pesan yang di-streaming saat halaman terbuka juga memiliki tampilan Deltas dari delta event-nya.
- Tools mencantumkan alat yang dikonfigurasikan untuk agen-agen sesi, beserta jumlah panggilan, kegagalan, dan durasi median; pilih sebuah alat untuk melihat panggilannya dan melompat ke salah satunya di transkrip.
- Resources mencantumkan file, repositori, dan penyimpanan memori yang di-mount pada path container-nya, termasuk memori di setiap penyimpanan dan perubahan yang dibuat sesi ini terhadapnya, ditambah file yang ditulis agen ke
/mnt/session/outputsdan track yang dilampirkan ke agen-agen sesi. - Threads mencantumkan setiap thread beserta status, ukuran konteks, dan biayanya. Pilih sebuah thread untuk melihat detailnya, seperti agen, model, penggunaan konteks, dan biaya.
Tambahkan ?event={event_id} ke URL sesi untuk membuka sesi pada event tertentu.
Dengan ant beta:sessions connect, Anda dapat membuka penampil yang sama dari CLI ant atau mengikuti sesi di terminal Anda. Lihat Menghubungkan ke sesi Managed Agents dari terminal Anda.
Tips debugging
- Periksa event sesi: Error sesi disampaikan melalui event
session.error
- Tinjau hasil alat: Kegagalan eksekusi alat sering kali menjelaskan perilaku agen yang tidak terduga
- Lacak penggunaan token: Pantau konsumsi token untuk mengoptimalkan prompt dan mengurangi biaya
- Gunakan prompt sistem: Tambahkan instruksi logging ke prompt sistem agar agen menjelaskan penalarannya
- Pecahkan masalah pratinjau: Jika stream yang memilih ikut serta dalam delta event tidak berperilaku seperti yang Anda harapkan, lihat Pecahkan masalah pratinjau