Penggunaan browser dan komputer dengan toolset SDK
Jalankan alat penggunaan browser atau alat penggunaan komputer dari SDK Python atau TypeScript. SDK menjalankan loop dan pemeriksaan yang Anda konfigurasikan, dan Anda menyediakan browser atau desktopnya.
Dengan browser toolset dan computer toolset di SDK Python dan TypeScript, Anda dapat membangun agen yang bekerja di browser web atau di desktop. Agen tersebut dapat menggunakan aplikasi yang tidak memiliki antarmuka terprogram. Melalui alat penggunaan browser, Claude menavigasi, membaca, dan berinteraksi dengan halaman web. Melalui alat penggunaan komputer, Claude melihat desktop dalam bentuk tangkapan layar dan menggunakan mouse serta keyboard.
SDK menghubungkan Claude ke browser atau desktop Anda. SDK menjalankan pemeriksaan yang Anda konfigurasikan, seperti kebijakan URL untuk navigate atau callback persetujuan. SDK mengirim setiap panggilan yang lolos ke driver Anda dan membangun tool_result dari apa yang dikembalikan driver. Tool runner dapat menjalankan loop agentik untuk Anda.
Anda menyediakan driver, yang mengoperasikan browser atau desktop. Mulailah dari driver milik penyedia. Dengan driver tersebut, sebuah agen hanya membutuhkan beberapa baris kode. Jika tidak ada yang cocok, tulis driver Anda sendiri dengan pustaka otomatisasi browser seperti Playwright.
Sebagian besar halaman ini membahas browser toolset. Toolset komputer membahas apa yang berbeda.
Menulis driver Anda sendiri adalah kasus lanjutan, yang dibahas di Mengimplementasikan driver. Repositori claude-quickstarts juga memiliki contoh driver browser dalam Python dan TypeScript. Driver tersebut mengendalikan Chromium melalui Chrome DevTools Protocol (CDP), dan bukan kode produksi.
Menggunakan driver dari penyedia
Dengan driver dari penyedia, seperti salah satu yang ada di Integrasi mitra, Anda dapat membangun agen dalam beberapa baris kode. Penyedia dalam contoh ini adalah rekaan. Ganti example_browser_provider dan ExampleBrowser dengan paket dan kelas driver milik penyedia sungguhan, lalu siapkan sesuai yang dijelaskan dokumentasi penyedia tersebut.
from anthropic import Anthropic
from example_browser_provider import ExampleBrowser # made-up provider
client = Anthropic()
with ExampleBrowser() as browser:
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser],
messages=[
{"role": "user", "content": "Open example.com and tell me its heading."}
],
)
for message in runner:
print(message)Berikan instance driver itu sendiri sebagai entri tools. Runner tidak pernah menutup toolset, sehingga satu instance dapat melayani beberapa run. Tutuplah setelah Anda selesai.
Untuk menulis driver Anda sendiri, lihat Mengimplementasikan driver.
Integrasi mitra
Browser Use, Browserbase, Daytona, dan E2B menerbitkan driver mereka sendiri untuk toolset SDK.
Driver Browser Use untuk browser toolset.
Driver Browserbase Stagehand untuk browser toolset.
Driver Daytona untuk computer toolset.
Driver E2B untuk browser toolset dan computer toolset.
Mitra kami juga menerbitkan kode di GitHub: quickstart Browser Use, toolset Browserbase, dan contoh E2B.
Menyesuaikan driver
Contoh-contoh di bagian ini menggunakan MyBrowser, driver dari contoh lanjutan di bagian selanjutnya halaman ini.
Mengaktifkan atau menonaktifkan anggota
configs menerima pengaturan per anggota yang dijelaskan di Mengonfigurasi toolset. Cantumkan hanya anggota yang Anda ubah:
# MyBrowser yang juga mengimplementasikan read_console
browser = MyBrowser(
backend, configs={"read_console": {"enabled": True}, "navigate": {"enabled": False}}
)SDK menolak panggilan ke anggota yang dinonaktifkan sebelum kode Anda berjalan. Mengaktifkan anggota yang tidak diimplementasikan oleh kelas Anda merupakan error konfigurasi, kecuali kelas tersebut meng-override execute.
Menambahkan hook sebelum dan sesudah
Override execute dan panggil execute milik parent. Kode sebelum panggilan itu berjalan setelah kebijakan URL, kebijakan file, dan confirm, dan kode tersebut dapat mengubah input. SDK tidak memeriksa input yang diubah lagi. Kode setelah panggilan menerima hasilnya, dan dapat mengubah hasil tersebut. Raise ToolError (throw di TypeScript) untuk menolak panggilan.
Meng-override execute mengubah anggota mana yang ditawarkan kepada Claude. SDK menganggap setiap anggota sudah diimplementasikan, sehingga Claude ditawari setiap anggota yang aktif secara default. MyBrowser dalam contoh lanjutan melayani tiga anggota, sehingga TracedBrowser berikut menawarkan kepada Claude anggota yang tidak dapat dilayaninya. Nonaktifkan anggota tersebut dengan configs sebelum Anda menggunakannya.
import time
class TracedBrowser(MyBrowser):
def execute(self, context, name, input):
started = time.monotonic()
result = super().execute(context, name, input)
elapsed_ms = (time.monotonic() - started) * 1000
call_id = context.tool_use.id if context.tool_use else "-"
log.info("%s %s %.0fms", call_id, name, elapsed_ms)
return redact(result) if name == "get_page_text" else resultMengimplementasikan driver
Driver adalah subclass Anda dari BetaAbstractBrowserToolset20260801. Di dalamnya, override anggota yang didukung browser Anda. Anggota yang tidak Anda implementasikan dikirim ke API sebagai dinonaktifkan. Jika Claude tetap memanggilnya, SDK mengembalikan error, dan run berlanjut. Meng-override execute mengubah anggota mana yang dikirim sebagai dinonaktifkan (Menambahkan hook sebelum dan sesudah). Setiap anggota menerima konteks panggilan dan input anggota sebagai objek bertipe, seperti BetaBrowserNavigateInput. Tipe input berasal dari anthropic.types.beta. Alat anggota mencantumkan field setiap input.
Di TypeScript, tulis anggota sebagai metode, bukan field arrow-function, karena SDK menemukannya pada prototype. Tulis anggota type sebagai type_ di TypeScript. Di Python, namanya type.
Mengembalikan hasil
Apa yang dikembalikan anggota menentukan apa yang dibaca Claude. Hasil yang berhasil diakhiri dengan blok browser_state yang dibangun dari laporan state Anda. Hasil error tidak membawa blok tersebut.
| Anggota | Mengembalikan | Yang dibaca Claude |
|---|---|---|
screenshot, zoom | BetaScreenshotResult | Satu blok gambar |
navigate | BetaBrowserNavigateResult | Navigated to {url} — {title} (HTTP {status}) |
new_tab, switch_tab, list_tabs, close_tab | Sebuah entri tab (new_tab, switch_tab), daftar entri tab (list_tabs), atau tidak ada (close_tab) | Blok browser_state saja |
read_page, get_page_text, find, read_console, read_network, javascript_exec | Sebuah string | String tersebut, atau (empty) untuk string kosong |
| Setiap anggota lainnya | Tidak ada, atau satu baris teks | Konfirmasi singkat, seperti Clicked., lalu baris yang dikembalikan dalam blok teks tersendiri |
Melaporkan state browser
SDK memanggil _browser_state setelah setiap panggilan yang mengembalikan hasil, termasuk panggilan yang ditolak dan gagal. Kembalikan setiap tab yang terbuka, dan apa yang berubah sejak laporan terakhir:
- Tab yang dibuka dan event unduhan.
- Sebuah
BetaNavigationRefuseduntuk setiap navigasi yang diblokir oleh request hook Anda. - Sebuah
BetaDialogDismisseduntuk setiap dialog native yang ditutup oleh driver Anda.
Masukkan semua ini ke dalam state_changes. Di Python, BetaNavigationRefused() dan BetaDialogDismissed(kind=..., message=...) berasal dari anthropic.tools.browser. Di TypeScript, keduanya adalah { type: "navigation_refused" } dan { type: "dialog_dismissed", kind, message }.
Dua yang terakhir bukan perubahan state API. SDK melaporkannya kepada Claude sebagai teks di luar blok browser_state: satu baris untuk semua navigasi yang ditolak, yang tidak menyertakan URL, dan satu baris untuk masing-masing dari tiga dialog pertama yang ditutup, lalu jumlah dialog lainnya.
Ketika ada tab yang terbuka, tepat satu tab harus aktif. Setiap anggota yang menerima tab_id harus bertindak pada tab yang disebutkannya. Batasan API pada laporan tercantum di Melacak tab dengan browser_state.
Claude membaca URL setiap tab, URL setiap unduhan, dan URL yang dikembalikan navigate Anda, sebagaimana ditulis oleh driver Anda. SDK tidak mem-parse URL ini. SDK mengubah jeda baris dan karakter kontrol lainnya menjadi spasi, memangkas ujung-ujungnya, dan memotong setiap URL pada 4.096 karakter. Jadi isi URL data:, path URL file:, serta nama pengguna atau kata sandi dalam URL semuanya sampai ke Claude.
Menangani error
| Di-raise oleh anggota atau SDK | Yang dibaca Claude | Run |
|---|---|---|
ToolError | Pesannya, sebagai hasil error | Berlanjut |
ToolsetUsageError | Tidak ada | Berhenti |
| Exception lainnya | ClassName: message di Python atau Error: message di TypeScript, sebagai hasil error | Berlanjut |
SDK me-raise ToolsetUsageError untuk error konfigurasi, penyalahgunaan SDK selama panggilan, atau panggilan setelah close. SDK juga me-raise-nya ketika _browser_state me-raise exception apa pun, bahkan ToolError. Tool runner dan tool_result tidak menangkapnya, sehingga exception tersebut sampai ke pemanggilnya.
SDK tidak menyembunyikan path lokal dalam teks error anggota, dalam baris yang dikembalikan oleh aksi seperti left_click, dalam error unduhan yang gagal, atau dalam pesan dialog yang ditutup.
ToolError dari kebijakan URL, kebijakan file, atau callable confirm Anda sampai ke Claude sebagaimana ditulis, jadi jangan sertakan path lokal dalam teksnya. Tangkap exception di anggota Anda, dan raise ToolError (throw di TypeScript) dengan teks Anda sendiri.
Memulai panggilan saat respons sedang di-streaming
Secara default, tool runner menjalankan panggilan dalam satu giliran setelah respons Claude berakhir. Dengan early start, tool runner dapat memulai panggilan browser atau komputer saat respons masih di-streaming.
Untuk mengaktifkannya, berikan stream=True dan run_tools_eagerly=True ke tool runner, seperti yang dilakukan contoh lanjutan. Dengan stream, setiap putaran loop Anda atas runner memberi Anda stream alih-alih pesan.
Early start mengubah kapan panggilan dapat dimulai, bukan cara toolset menjalankannya:
- Pemeriksaan tetap berjalan lebih dulu: SDK memanggil kebijakan URL, kebijakan file, dan
confirmAnda sebelum panggilan berjalan. Dengan early start, SDK dapat memanggilconfirmsaat respons masih di-streaming. - Panggilan tetap berjalan satu per satu: Sebuah toolset menjalankan panggilannya sesuai urutan yang ditulis Claude. Panggilan yang gagal tetap menghentikan panggilan berikutnya dari toolset tersebut dalam giliran itu.
- Panggilan yang sudah dimulai tidak dapat dibatalkan: Jika respons kemudian terpotong, misalnya pada
max_tokens, atau loop Anda berhenti lebih awal, aksi tersebut tetap terjadi, dan Claude tidak pernah membaca hasilnya.
Menjalankan tanpa tool runner
Berikan instance di tools (browser.toJSON() di TypeScript), dan jawab setiap panggilan anggota dengan tool_result. Alat penggunaan browser mengharuskan Anda berhenti pada panggilan gagal pertama (Tindakan batch).
Loop ini menggunakan MyBrowser dan kebijakan URL dari contoh lanjutan. Setelah panggilan gagal, loop ini menjawab panggilan berikutnya dalam giliran tersebut tanpa menjalankannya:
from anthropic.types.beta import BetaMessageParam, BetaToolResultBlockParam
NOT_EXECUTED = "Not executed: an earlier action in this turn failed."
MAX_TURNS = 10
with MyBrowser(backend, url_policy=url_policy) as browser:
messages: list[BetaMessageParam] = [{"role": "user", "content": "Open example.com"}]
for _ in range(MAX_TURNS):
response = client.beta.messages.create(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser],
messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
calls = [
block
for block in response.content
if block.type == "tool_use" and block.toolset_name == browser.toolset_name
]
if not calls:
break
results: list[BetaToolResultBlockParam] = []
failed = False
for call in calls:
if failed:
# Setelah panggilan gagal, sisa giliran dijawab, bukan dijalankan.
results.append(
{
"type": "tool_result",
"tool_use_id": call.id,
"toolset_name": call.toolset_name,
"content": NOT_EXECUTED,
"is_error": True,
}
)
continue
result = browser.tool_result(call)
failed = bool(result.get("is_error"))
results.append(result)
messages.append({"role": "user", "content": results})Setiap jawaban untuk panggilan yang dilewati membawa is_error, toolset_name dari panggilan tersebut, dan teks persis yang diwajibkan oleh Tindakan batch. Tool runner mengirimkan jawaban yang sama.
Contoh lanjutan: menulis driver Anda sendiri
Contoh ini menggabungkan bagian-bagian utama dari Mengimplementasikan driver ke dalam satu driver, MyBrowser, dan menjalankannya dengan tool runner dan early start. MyBrowser mengimplementasikan navigate, screenshot, dan left_click, ditambah _browser_state, laporan state yang dibutuhkan setiap driver.
backend mewakili wrapper Anda sendiri di sekitar pustaka otomatisasi browser, seperti Playwright. Kebijakan URL-nya adalah contoh. Menulis kebijakan URL menjelaskannya.
import re
from urllib.parse import urlsplit
from anthropic import Anthropic
from anthropic.tools import ToolError
from anthropic.tools.browser import (
BetaAbstractBrowserToolset20260801,
BetaBrowserNavigateResult,
BetaBrowserState,
BetaScreenshotResult,
BetaToolsetCallContext,
BetaURLContext,
)
from anthropic.types.beta import (
BetaBrowserLeftClickInput,
BetaBrowserNavigateInput,
BetaBrowserScreenshotInput,
BetaBrowserStateTabEntryParam,
)
class MyBrowser(BetaAbstractBrowserToolset20260801):
def __init__(self, backend, **options):
super().__init__(**options)
self.backend = backend
def _browser_state(self, context: BetaToolsetCallContext) -> BetaBrowserState:
return BetaBrowserState(
tabs=[
BetaBrowserStateTabEntryParam(
tab_id=tab.id,
title=tab.title,
url=tab.url,
active=tab.id == self.backend.active,
)
for tab in self.backend.tabs()
],
state_changes=self.backend.drain_changes(),
)
def navigate(
self, context: BetaToolsetCallContext, input: BetaBrowserNavigateInput
) -> BetaBrowserNavigateResult:
# input.url berisi "back", "forward", "reload", atau URL yang diizinkan oleh kebijakan URL
# Anda, persis seperti yang ditulis Claude. Seperti is_allowed, backend.goto menambahkan https://
# ke URL yang tidak diawali dengan skema.
page = self.backend.goto(input.url, input.tab_id)
return BetaBrowserNavigateResult(
url=page.url, status=page.status, title=page.title
)
def screenshot(
self, context: BetaToolsetCallContext, input: BetaBrowserScreenshotInput
) -> BetaScreenshotResult:
data = self.backend.png_base64(input.tab_id)
return BetaScreenshotResult(data=data, media_type="image/png")
def left_click(
self, context: BetaToolsetCallContext, input: BetaBrowserLeftClickInput
) -> None:
# Tidak ada yang dikembalikan: Claude membaca "Clicked."
self.backend.click(input.target, input.tab_id)
def close(self) -> None:
super().close() # first, so no call is still using the browser when it closes
if not self.backend.closed:
self.backend.close()
ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)
def is_allowed(url: str) -> bool:
# Sekadar contoh, bukan kebijakan untuk produksi. Lihat "Write a URL policy".
if url.lower() == "about:blank":
return True
with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
# Browser membaca "\" sebagai "/" dalam URL web.
try:
parts = urlsplit(with_scheme.replace("\\", "/"))
except ValueError:
return False
host = parts.hostname or ""
listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
return parts.scheme in ("http", "https") and listed
def url_policy(context: BetaURLContext, url: str) -> None:
if not is_allowed(url):
raise ToolError(f"blocked: {url} is not on an allowed host")
client = Anthropic()
with MyBrowser(backend, url_policy=url_policy) as browser:
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser],
messages=[
{
"role": "user",
"content": "Open example.com and tell me the page heading.",
}
],
stream=True,
run_tools_eagerly=True, # so a call can start before the response ends
)
for stream in runner:
print(stream.get_final_message())Menjalankan toolset dengan aman
Tindakan Claude berikutnya bergantung pada halaman yang dibacanya. Sebuah halaman, atau teks yang disuntikkan ke dalamnya, dapat mencoba menjangkau layanan internal atau menarik file dari host. Halaman tersebut juga dapat mencoba memicu tindakan dengan efek nyata. Sebelum Anda menjalankan driver terhadap apa pun selain browser sekali pakai, lakukan enam langkah berikut:
- Tulis kebijakan URL yang menolak setiap URL
navigateyang tidak dibutuhkan tugas. - Intersep permintaan di driver, dan periksa setiap request dengan aturan yang sama.
- Terapkan kebijakan egress pada container, sehingga jaringan memblokir apa yang tidak dapat dilihat driver.
- Batasi unggahan dan unduhan, atau biarkan unggahan nonaktif.
- Batasi anggota yang berdampak dengan
confirm. - Isolasi host browser dalam container atau VM khusus untuk setiap sesi.
SDK menerapkan langkah 1, 4, dan 5 melalui kebijakan dan callable yang Anda berikan. Langkah 2, 3, dan 6 menjadi tanggung jawab driver dan deployment Anda. Tindakan pencegahan di Pertimbangan keamanan juga berlaku.
Menulis kebijakan URL
Kebijakan URL adalah fungsi yang Anda berikan sebagai url_policy. Anda yang menulis kebijakan tersebut, dan Anda bertanggung jawab untuk membuatnya siap produksi. Sebelum navigate Anda berjalan, SDK memanggil kebijakan dengan konteks panggilan dan URL sebagaimana ditulis Claude.
Jangan kembalikan apa pun untuk mengizinkan URL. Untuk menolaknya, raise ToolError (throw di TypeScript). Claude membaca pesan error tersebut, dan navigate tidak berjalan. Kebijakan hanya memeriksa URL dalam setiap panggilan navigate. Kebijakan ini bukan kontrol jaringan.
Ini adalah kebijakan dari contoh lanjutan. Kebijakan ini mengizinkan about:blank, ditambah dua situs beserta subdomainnya melalui http dan https:
import re
from urllib.parse import urlsplit
from anthropic.tools import ToolError
from anthropic.tools.browser import BetaURLContext
ALLOWED_HOSTS = ("example.com", "iana.org")
SCHEME_PREFIX = re.compile(r"[a-z][a-z0-9+.-]*:", re.IGNORECASE)
def is_allowed(url: str) -> bool:
# Sebuah contoh, bukan kebijakan produksi.
if url.lower() == "about:blank":
return True
with_scheme = url if SCHEME_PREFIX.match(url) else f"https://{url}"
# Browser membaca "\" sebagai "/" dalam URL web.
try:
parts = urlsplit(with_scheme.replace("\\", "/"))
except ValueError:
return False
host = parts.hostname or ""
listed = any(host == name or host.endswith(f".{name}") for name in ALLOWED_HOSTS)
return parts.scheme in ("http", "https") and listed
def url_policy(context: BetaURLContext, url: str) -> None:
if not is_allowed(url):
raise ToolError(f"blocked: {url} is not on an allowed host")
browser = MyBrowser(backend, url_policy=url_policy)SDK memanggil kebijakan hanya untuk navigate, dan tidak untuk "back", "forward", atau "reload". Claude dapat menghilangkan skema, seperti pada example.com, sehingga sebelum membaca host, contoh ini menambahkan https:// ke URL yang tidak diawali skema. navigate Anda menerima URL sebagaimana ditulis Claude. Tambahkan skema di sana dengan pengujian yang sama, sehingga browser membuka URL yang diperiksa oleh kebijakan Anda.
Contoh ini tidak berkualitas produksi. Kebijakan produksi membutuhkan jauh lebih banyak, di area seperti berikut:
- Tautan yang diklik Claude, formulir yang dikirimkannya, redirect, dan request yang dibuat halaman dengan sendirinya. Hanya driver Anda yang melihatnya. Lihat Mengintersep permintaan di driver.
- Hostname dan URL yang berasal dari konten halaman, yang dapat dipilih oleh penyerang.
- Mem-parse setiap URL seperti yang dilakukan browser, bukan menemukan hostname dengan pemisahan string biasa.
- Skema URL. SDK tidak memeriksanya, jadi tolak setiap skema yang tidak dimaksudkan untuk dibuka oleh driver Anda.
- Aturan egress pada container, untuk apa yang tidak dapat dilihat oleh pemeriksaan URL mana pun. Lihat Menerapkan kebijakan egress pada container.
Jika Anda memberikan url_policy=None, SDK menolak setiap navigate ke sebuah URL, sehingga None yang dibaca dari konfigurasi Anda tidak dapat secara tidak sengaja membiarkan URL tidak diperiksa. Di TypeScript, undefined membiarkan opsi tidak diatur, sama seperti tidak memberikannya.
Mengintersep permintaan di driver
Kebijakan URL hanya menilai URL dalam panggilan navigate. Tautan yang diklik Claude, formulir yang dikirimkannya, redirect, atau gambar atau skrip pada halaman yang diizinkan masih dapat menjangkau host yang akan ditolak oleh kebijakan. Gunakan aturan egress untuk memblokir host tersebut.
Driver dengan intersepsi request juga dapat memeriksa request tersebut di request hook-nya, yaitu fungsi yang dipanggil pustaka otomatisasi Anda sebelum setiap request. Panggil fungsi yang digunakan kebijakan Anda, sehingga Anda menulis aturannya sekali saja:
class MyBrowser(BetaAbstractBrowserToolset20260801):
...
# Didaftarkan pada konteks browser Playwright dengan context.route("**/*", self._guard)
def _guard(self, route):
# is_allowed adalah fungsi dari "Write a URL policy"
if not is_allowed(route.request.url):
return route.abort("blockedbyclient")
route.continue_()Request hook seperti ini tidak melihat handshake WebSocket, request service-worker, atau lompatan redirect. Blokir service worker. Periksa handshake WebSocket dan lompatan redirect dengan hook yang dapat melihatnya. Contoh kebijakan menolak URL ws:// dan wss://, jadi ubah ws:// menjadi http:// dan wss:// menjadi https:// sebelum Anda memberikan URL WebSocket ke is_allowed.
Menerapkan kebijakan egress pada container
Intersepsi tidak dapat melihat setiap permintaan yang dibuat browser, dan kebijakan URL tidak melihat ke mana suatu nama di-resolve. Kebijakan "egress" (lalu lintas keluar) yang ditegakkan oleh jaringan container mencakup keduanya:
- Blokir rentang alamat link-local dan privat di IPv4 dan IPv6. Itu termasuk alamat metadata cloud
169.254.169.254. - Izinkan koneksi keluar hanya ke host yang dibutuhkan tugas. Jika aturan Anda mencocokkan alamat IP, resolve hostname yang Anda izinkan saat container dimulai.
- Izinkan DNS hanya ke resolver container.
Aturan yang diterapkan di luar container browser, seperti Kubernetes NetworkPolicy atau firewall cloud, tidak melihat loopback di dalamnya. Dengan hanya aturan tersebut, sebuah halaman dapat menjangkau apa pun yang mendengarkan di sana, termasuk port DevTools, yang menampilkan daftar tab browser yang terbuka dan alamat yang mengendalikan masing-masing tab.
Membatasi unggahan dan unduhan
file_upload nonaktif secara default. Tanpa file_policy, SDK menolak setiap unggahan yang memberikan path atau ID dokumen. Untuk mengaktifkan unggahan, berikan BetaLocalFilePolicy dengan satu direktori unggahan yang hanya berisi file untuk tugas tersebut:
from anthropic.tools.browser import BetaLocalFilePolicy
# MyBrowser yang juga mengimplementasikan file_upload
browser = MyBrowser(
backend,
configs={"file_upload": {"enabled": True}},
confirm=make_confirm(), # required for file_upload; see Gate consequential members
url_policy=url_policy,
file_policy=BetaLocalFilePolicy(
upload_roots=["/task/uploads"],
download_dir="/task/downloads",
expose_download_paths=False,
),
)SDK me-resolve setiap path unggahan, mengikuti symlink, dan menolak path apa pun di luar root unggahan. Kebijakan file me-raise error konfigurasi untuk direktori unduhan yang merupakan root unggahan, berada di dalamnya, atau berisi root unggahan. Pemeriksaan itu dapat melewatkan symlink, jadi jangan biarkan symlink membuat direktori unduhan sama dengan root unggahan, atau menempatkan salah satunya di dalam yang lain. SDK menyertakan path unduhan dalam laporan status hanya ketika expose_download_paths bernilai true dan file berada di dalam direktori unduhan.
Kebijakan file bawaan memeriksa path pada filesystem proses yang menjalankan SDK. Kebijakan ini hanya melindungi browser yang berbagi filesystem tersebut. Untuk browser remote, ikuti Browser remote dan hosted sebagai gantinya.
Siapkan unduhan dengan cara berikut:
- Buat direktori unduhan sendiri dengan mode
0700, dan mount dengannoexec,nosuid,nodev. - Jauhkan direktori tersebut dari jangkauan alat lain yang dapat dipanggil Claude, seperti shell atau alat file.
- Dalam perubahan state
download_failed, tuliserrorsebagai frasa tetap. Teks exception dapat berisi path atau URL. - Jangan membaca file yang diunduh ke dalam percakapan, atau menjalankannya, sampai seseorang memutuskan untuk melakukannya.
Membatasi anggota yang berdampak
javascript_exec dan file_upload nonaktif secara default. Jika Anda mengaktifkan salah satunya tanpa callable confirm, constructor me-raise error konfigurasi. Dengan callable confirm, SDK memanggilnya sebelum setiap panggilan yang akan berjalan. Tanpanya, tidak ada yang ditanyakan. Di TypeScript, confirm: null tidak me-raise error konfigurasi, dan SDK menolak setiap panggilan ke anggota mana pun.
Kembalikan True untuk menjalankan panggilan, atau False untuk menolaknya. Ketika callable Anda bertanya kepada seseorang, tunjukkan anggota, URL halaman, dan input panggilan. Pertama, escape setiap karakter di luar ASCII yang dapat dicetak dalam URL dan input, karena keduanya dapat membawa teks dari halaman.
Contoh ini menanyakan tentang dua anggota yang dilindungi melalui fungsi ask_user Anda sendiri, dan menyetujui sisanya:
import json
from collections.abc import Callable
from urllib.parse import urlsplit
from anthropic.tools.browser import BetaConfirmContext
GATED = {"javascript_exec", "file_upload"}
def shown(value: object) -> str:
"""A value as JSON, with every character outside printable ASCII escaped."""
return json.dumps(value, ensure_ascii=True, indent=2)
def make_confirm() -> Callable[[BetaConfirmContext], bool]:
granted: set[tuple[str, str, str]] = set()
def confirm(context: BetaConfirmContext) -> bool:
name = context.member
if name not in GATED:
return True
detail = shown(context.input.to_dict())
page = context.tab_url
where = shown(page) if page else "a tab with no URL"
question = f"Allow {name} on {where}?\n{detail}"
if page is None or urlsplit(page).scheme not in ("http", "https"):
return ask_user(question) # no web origin: ask every time
# Persetujuan hanya berlaku untuk input persis ini di halaman ini.
key = (name, page, detail)
if key not in granted and ask_user(question):
granted.add(key)
return key in granted
return confirm
# MyBrowser yang juga mengimplementasikan javascript_exec dan file_upload
# file_upload juga memerlukan file_policy dari "Confine uploads and downloads"
browser = MyBrowser(
backend,
configs={"javascript_exec": {"enabled": True}, "file_upload": {"enabled": True}},
confirm=make_confirm(),
url_policy=url_policy,
)Setiap panggilan ke make_confirm() mengembalikan callable tanpa persetujuan. Panggil sekali untuk setiap toolset, dan berikan setiap pengguna toolset mereka sendiri.
Persetujuan mencakup halaman sebagaimana ditunjukkan oleh laporan state terakhir, dan halaman dapat berubah sebelum panggilan berjalan. Pembelian, pesan yang dikirim, dan persyaratan yang diterima terjadi melalui anggota biasa seperti left_click dan type, sehingga confirm tidak dapat memilahnya berdasarkan nama. Agar seseorang menyetujui tindakan tersebut, tanyakan juga tentang anggota-anggota itu.
Jangan aktifkan javascript_exec kecuali aturan egress membatasi koneksi keluar browser ke host yang dibutuhkan tugas. Sebuah skrip dapat mengirim konten halaman ke alamat mana pun yang dapat dijangkau browser.
URL javascript: dalam panggilan navigate juga menjalankan skrip di halaman saat ini, bahkan ketika javascript_exec nonaktif. confirm melihat panggilan itu sebagai navigate, sehingga callable confirm dalam contoh akan menyetujuinya. Driver Anda harus menolak URL javascript:. Dalam contoh, kebijakan URL menolaknya sebelum confirm berjalan.
Mengisolasi host browser
Jalankan browser dalam container atau VM khusus dengan hak akses minimal untuk setiap sesi:
- Jalankan sebagai pengguna non-root, dengan filesystem root read-only jika browser mengizinkannya.
- Jangan mount apa pun dari host selain direktori unggahan dan unduhan yang Anda konfigurasikan, jika ada.
- Jauhkan kredensial dari lingkungan, dan mulai dari profil browser yang baru.
- Jangan berbagi filesystem dengan alat lain yang dapat dipanggil Claude.
Jalankan kode yang memanggil API di luar container browser, karena kode tersebut menyimpan kunci API Anda dan percakapan. Tool runner dan tool_result keduanya menjalankan toolset dalam proses kode tersebut, sehingga browser tidak berbagi filesystem toolset. Perlakukan browser sebagai remote: Browser remote dan hosted berlaku.
Perlakukan semua yang dikembalikan halaman sebagai tidak tepercaya, termasuk teks halaman, screenshot, entri konsol dan jaringan, judul tab, dan nama unduhan.
Browser remote dan hosted
Beberapa browser tidak berbagi filesystem dengan proses yang menjalankan SDK. Contohnya adalah browser di container lain, browser yang Anda jangkau melalui URL DevTools, dan browser dari layanan browser hosted. Dengan browser-browser ini, kebijakan URL, intersepsi permintaan, dan confirm tetap berjalan di proses Anda.
Dengan browser hosted, penyedia mengontrol egress dan isolasi host. Kebijakan egress Anda sendiri tidak berlaku di sana, sehingga hook permintaan driver adalah satu-satunya pemeriksaan Anda atas permintaan browser. Cari tahu apa saja yang dapat dijangkau oleh jaringan browser tersebut.
Pemeriksaan path bawaan tidak melindungi browser remote. BetaLocalFilePolicy memeriksa path pada filesystem proses yang menjalankan SDK, sedangkan browser membaca dan menulis filesystem-nya sendiri.
SDK tidak dapat mendeteksi bahwa sebuah browser bersifat remote. Jadi untuk browser remote, biarkan file_upload nonaktif kecuali driver Anda memeriksa path unggahan di tempat browser berjalan, dengan BetaFilePolicy miliknya sendiri. BetaFilePolicy memeriksa path dan ID dokumen setiap unggahan, dan memutuskan apakah Claude melihat path unduhan.
Buat browser remote menolak unduhan, kecuali host-nya sendiri memiliki penyiapan unduhan dari Membatasi unggahan dan unduhan.
Jauhkan kunci API penyedia dan URL koneksi sesi, yang dapat berisi kunci, dari log, hasil alat, dan teks error. Jika penyedia merekam sesi, rekaman tersebut merupakan salinan lain dari semua yang dilihat dan diketik Claude, dan ketentuan retensi penyedia berlaku untuknya.
Toolset komputer
BetaAbstractComputerToolset20260801 adalah kelas untuk alat penggunaan komputer. Driver adalah subclass dengan satu metode per alat, seperti screenshot atau left_click. Mulailah dari driver mitra di bawah Integrasi mitra. Untuk menulis driver Anda sendiri, repositori claude-quickstarts memiliki contoh VNC minimal dalam Python dan TypeScript. Contoh ini mengendalikan desktop melalui VNC, dan bukan kode produksi.
Kelas ini menerima configs, confirm, dan tool_configs, dan Anda memberikannya seperti pada kelas browser. Menyesuaikan driver, Menangani error, Memulai panggilan saat respons sedang di-streaming, dan Menjalankan tanpa tool runner juga berlaku untuknya, dengan perbedaan berikut:
- Opsi khusus browser: Kelas ini tidak memiliki kebijakan URL, kebijakan file, maupun laporan status.
configs: Setiap alat yang Anda implementasikan aktif secara default. Pengaturan per alat tercantum di Parameter alat.confirm: Jika kelas Anda mengimplementasikantype,key, atauhold_key, constructor me-raise error konfigurasi kecuali Anda memberikan callableconfirmatau menonaktifkan alat tersebut denganconfigs. Kelas yang meng-overrideexecutedianggap mengimplementasikan ketiganya.- Panggilan yang dilewati: Setelah panggilan komputer gagal, tool runner menjawab panggilan komputer berikutnya dalam giliran tersebut dengan
Not executed: an earlier computer action in this turn failed.Panggilan browser mendapat teks yang berbeda. Dalam loop yang Anda tulis, jawab panggilan komputer yang dilewati dengan teks persis tersebut (Aksi batch). execute: Di TypeScript, overrideexecutememberi tipenamesebagaiBetaComputerMemberNamedaninputsebagaiBetaComputerMemberInput. Keduanya berasal dari@anthropic-ai/sdk/resources/beta.- Teks error: SDK tidak menyembunyikan path lokal dalam teks error alat komputer atau dalam teks yang dikembalikan alat. Claude membaca keduanya, dipotong pada 4.096 karakter. Tangkap exception di alat Anda, dan raise
ToolError(throw di TypeScript) dengan teks Anda sendiri.
Menulis driver desktop
Driver ini mengimplementasikan lima dari 17 alat yang tercantum di bawah Tindakan yang tersedia. Alat yang tidak Anda implementasikan dikirim ke API sebagai dinonaktifkan, seperti yang dijelaskan di Mengimplementasikan driver. Dalam contoh ini, display mewakili wrapper Anda sendiri di sekitar apa pun yang mengendalikan desktop, seperti klien VNC atau xdotool, dan ask_user mewakili fungsi Anda sendiri yang bertanya kepada seseorang.
from anthropic import Anthropic
from anthropic.tools.computer import (
BetaAbstractComputerToolset20260801,
BetaComputerCursorPositionResult,
BetaScreenshotResult,
BetaToolsetCallContext,
)
from anthropic.types.beta import (
BetaComputerCursorPositionInput,
BetaComputerKeyInput,
BetaComputerLeftClickInput,
BetaComputerScreenshotInput,
BetaComputerTypeInput,
)
class MyDesktop(BetaAbstractComputerToolset20260801):
def __init__(self, display, **options):
super().__init__(**options)
self.display = display
def screenshot(
self, context: BetaToolsetCallContext, input: BetaComputerScreenshotInput
) -> BetaScreenshotResult:
# Koordinat Claude adalah posisi piksel dalam gambar ini.
data = self.display.png_base64()
return BetaScreenshotResult(data=data, media_type="image/png")
def cursor_position(
self, context: BetaToolsetCallContext, input: BetaComputerCursorPositionInput
) -> BetaComputerCursorPositionResult:
x, y = self.display.cursor()
return BetaComputerCursorPositionResult(x=x, y=y)
def left_click(
self, context: BetaToolsetCallContext, input: BetaComputerLeftClickInput
) -> None:
# input.coordinate berupa [x, y], atau None untuk mengklik di posisi kursor. input.text adalah
# tombol pengubah yang ditahan, misalnya "shift". Tidak ada nilai kembalian: Claude membaca "Clicked."
self.display.click(input.coordinate, input.text)
def type(
self, context: BetaToolsetCallContext, input: BetaComputerTypeInput
) -> None:
self.display.type(input.text)
def key(self, context: BetaToolsetCallContext, input: BetaComputerKeyInput) -> None:
self.display.press(input.text, input.repeat or 1)
def close(self) -> None:
super().close() # first, so no call is still using the desktop when it closes
self.display.close()
client = Anthropic()
with MyDesktop(
display,
# wajib untuk type dan key; lihat Menjalankan toolset komputer dengan aman
confirm=lambda context: ask_user(f'Allow the "{context.member}" tool?'),
) as desktop:
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[desktop],
messages=[
{"role": "user", "content": "Open the calculator and compute 17 * 23."}
],
)
for message in runner:
print(message)Empat alat tidak memiliki field input: screenshot, cursor_position, left_mouse_down, dan left_mouse_up. Setiap tipe input, termasuk milik keempatnya, berasal dari anthropic.types.beta.
Di TypeScript, tulis alat sebagai metode, bukan field arrow-function. Mengimplementasikan driver memberikan alasannya.
Menskalakan koordinat dan mengatur ukuran gambar
Setiap titik yang dikirim Claude adalah posisi piksel dalam screenshot yang Anda kembalikan: setiap coordinate dan start_coordinate, serta kedua sudut dari region pada zoom. SDK meneruskannya ke alat Anda tanpa perubahan.
Jika screenshot Anda berukuran berbeda dari layar, lakukan konversi ke dua arah di driver Anda: titik dari Claude ke piksel layar, dan posisi kursor yang Anda laporkan ke piksel screenshot. Pertahankan ukuran screenshot yang sama selama seluruh sesi, dan berikan rasio aspek yang sama dengan layar:
from anthropic.tools import ToolError
SCREENSHOT_SIZE = (1280, 720) # the size of every full screenshot the driver returns
DISPLAY_SIZE = (2560, 1440)
def to_display(coordinate: list[int]) -> tuple[int, int]:
x, y = coordinate
width, height = SCREENSHOT_SIZE
if not (0 <= x < width and 0 <= y < height):
# Tolak titik tersebut. Memindahkannya ke dalam rentang akan mengklik lokasi yang tidak dipilih Claude.
raise ToolError(f"[{x}, {y}] is outside the {width}x{height} screenshot")
return x * DISPLAY_SIZE[0] // width, y * DISPLAY_SIZE[1] // height
def to_screenshot(x: int, y: int) -> tuple[int, int]:
width, height = DISPLAY_SIZE
return x * SCREENSHOT_SIZE[0] // width, y * SCREENSHOT_SIZE[1] // height
to_display([640, 360]) # (1280, 720)Panggil to_display pada setiap coordinate dan start_coordinate sebelum Anda menindaklanjutinya, dan to_screenshot pada posisi yang dilaporkan oleh cursor_position. Klik atau scroll tanpa coordinate bertindak pada posisi kursor, sehingga tidak ada yang perlu dikonversi. Skalakan region pada zoom dengan faktor yang sama. Gambar zoom tidak mengubah hal ini: titik berikutnya yang dikirim Claude tetap merupakan posisi piksel dalam screenshot penuh.
SDK tidak pernah membaca atau mengubah ukuran gambar. API menolak gambar yang melebihi batas model, yang mengakhiri run. Skalakan setiap screenshot dan gambar zoom di driver. Lihat Sesuaikan ukuran tangkapan layar agar sesuai batas gambar. Pada run yang panjang, satu permintaan dapat memuat lebih dari 20 gambar, dan API kemudian menerapkan batas yang lebih ketat pada setiap gambar. Lihat Mengelola riwayat tangkapan layar.
Pemeriksaan input dan hasil
Jangan mengandalkan SDK untuk memeriksa input suatu panggilan. Minta driver Anda memeriksa field yang digunakannya dan menolak panggilan yang tidak dapat dijalankannya: raise ToolError (throw di TypeScript). Claude membaca pesannya dan dapat mengirim panggilan yang sudah diperbaiki.
Kedua SDK tidak membatasi duration, jumlah repeat, maupun scroll_amount. Langkah 4 di bawah Menjalankan computer toolset dengan aman membahasnya.
Apa yang dikembalikan alat menentukan apa yang dibaca Claude:
| Alat | Mengembalikan | Yang dibaca Claude |
|---|---|---|
screenshot, zoom | BetaScreenshotResult | Satu blok gambar |
cursor_position | BetaComputerCursorPositionResult | X={x},Y={y} |
| Setiap alat lainnya | Tidak ada, atau satu baris teks | Konfirmasi singkat, seperti Clicked., lalu baris yang dikembalikan dalam blok teks tersendiri |
Menjalankan computer toolset dengan aman
Apa yang ada di layar mengarahkan tindakan Claude berikutnya. Sebuah halaman atau notifikasi dapat mencoba membuat Claude mengetik ke jendela yang salah, atau memicu tindakan dengan efek nyata.
Sebelum Anda menjalankan driver pada apa pun selain mesin sekali pakai, lakukan lima langkah berikut:
- Isolasi desktop. Jalankan di container atau VM khusus dengan hak akses minimal untuk setiap sesi, tanpa kredensial dan tanpa mount host. Instal hanya aplikasi yang dibutuhkan tugas. Jalankan kode yang memanggil API di luarnya.
- Batasi tindakan yang memiliki efek nyata dengan
confirm. Pembelian atau pesan yang terkirim hanyalahleft_click,type, ataukeybiasa, danconfirmmenerima alat beserta inputnya, bukan layarnya. Jadi pilih alat mana yang memerlukan persetujuan berdasarkan aplikasi yang dapat dijangkau Claude di desktop. - Jauhkan terminal, dialog run, dan launcher dari desktop kecuali tugas membutuhkannya. Ketika salah satunya memiliki fokus keyboard,
type,key, danhold_keymenjalankan apa pun yang diketik Claude, dan sebuah klik dapat memulai program. Jika tugas membutuhkannya, minta persetujuan seseorang sebelum input keyboard dan klik, dan isolasi desktop sehingga sebuah perintah tidak dapat menjangkau lebih jauh dari yang dibutuhkan tugas. - Tolak input yang tidak seharusnya dipenuhi desktop Anda, seperti titik di luar screenshot,
waitselama beberapa menit, ataurepeatatauscroll_amountyang sangat besar. RaiseToolError(throw di TypeScript) alih-alih memindahkan nilainya ke dalam rentang. - Perlakukan semua yang ada di layar sebagai tidak tepercaya, termasuk judul jendela dan teks clipboard. Jangan menjalankannya atau meneruskannya tanpa diperiksa.
SDK menerapkan langkah 2 melalui callable yang Anda berikan. Langkah-langkah lainnya bergantung pada driver dan deployment Anda. Tindakan pencegahan di bawah Pertimbangan keamanan juga berlaku.
confirm berikut meminta persetujuan seseorang sebelum input keyboard apa pun, melalui fungsi ask_user milik Anda sendiri, dan menyetujui setiap panggilan lainnya. context.member adalah nama alat. Agar seseorang juga menyetujui klik, atur ASK_FIRST ke KEYBOARD | CLICKS.
from anthropic.tools.computer import BetaComputerConfirmContext
KEYBOARD = {"type", "key", "hold_key"}
CLICKS = {
"left_click",
"right_click",
"middle_click",
"double_click",
"triple_click",
"left_click_drag",
"left_mouse_down",
"left_mouse_up",
}
ASK_FIRST = KEYBOARD
def confirm(context: BetaComputerConfirmContext) -> bool:
if context.member not in ASK_FIRST:
return True
# yang ditampilkan adalah fungsi dari "Gate consequential members".
detail = shown(context.input.to_dict())
return ask_user(f'Allow the "{context.member}" tool with this input?\n{detail}')
desktop = MyDesktop(display, confirm=confirm)Panggilan yang ditolak mendapatkan hasil error, dan run tetap berlanjut. Untuk panggilan type yang ditolak, Claude membaca The user did not grant permission to run 'type'. Do not retry it unless the user asks you to.
confirm ini menyetujui setiap alat yang tidak ada di ASK_FIRST, termasuk alat apa pun yang Anda tambahkan nanti. Untuk fail closed, setujui hanya alat yang tidak berpengaruh pada desktop (screenshot, zoom, cursor_position, dan wait) dan minta persetujuan untuk setiap alat lainnya.
Menggunakan kedua toolset dalam satu permintaan
Berikan kedua instance di tools. Setiap panggilan membawa toolset_name-nya, browser atau computer, dan tool runner mengirim panggilan ke instance yang sesuai. Setelah panggilan gagal, tool runner hanya melewati panggilan berikutnya dalam giliran tersebut ke toolset yang sama, sehingga panggilan komputer yang gagal tidak melewati panggilan browser dalam giliran tersebut.
# MyBrowser, backend, dan url_policy berasal dari contoh lanjutan.
# confirm adalah fungsi dari "Run the computer toolset safely".
with (
MyBrowser(backend, url_policy=url_policy) as browser,
MyDesktop(display, confirm=confirm) as desktop,
):
runner = client.beta.messages.tool_runner(
model="claude-opus-5-5",
max_tokens=1024,
tools=[browser, desktop],
messages=[
{
"role": "user",
"content": "Copy the heading on example.com into the open text editor.",
}
],
stream=True,
run_tools_eagerly=True, # so a call can start before the response ends
)
for stream in runner:
print(stream.get_final_message())Dalam loop yang Anda tulis sendiri, jawab setiap panggilan dengan tool_result pada instance yang toolset_name-nya cocok dengan toolset_name panggilan tersebut. Dalam setiap giliran, lacak panggilan yang gagal untuk setiap toolset secara terpisah, dan jawab panggilan yang dilewati dengan teks milik toolset tersebut. Menjalankan tanpa tool runner memuat teks untuk browser, dan poin Panggilan yang dilewati di bagian atas bagian ini memuat teks untuk computer.
Referensi
Opsi konstruktor memiliki arti yang sama di kedua SDK:
| Python | TypeScript | Mengatur |
|---|---|---|
configs | configs | Anggota mana yang diaktifkan |
confirm | confirm | Callable yang menyetujui atau menolak setiap panggilan |
url_policy | urlPolicy | Kebijakan URL Anda, yang dipanggil SDK sebelum setiap navigate ke sebuah URL |
file_policy | filePolicy | Root unggahan dan eksposur path unduhan |
tool_configs | toolConfigs | Field untuk entri tools, seperti cache_control |
Metode _browser_state | browserState | Laporan state |
Kelas browser menerima setiap opsi dalam tabel. Kelas komputer menerima configs, confirm, dan tool_configs.
Anda tidak dapat mengubah opsi setelah konstruksi. Panduan browser toolset Python, panduan browser toolset TypeScript, panduan computer toolset Python, dan panduan computer toolset TypeScript menjelaskan setiap opsi. Panduan Python juga membahas kelas async, BetaAsyncAbstractBrowserToolset20260801 dan BetaAsyncAbstractComputerToolset20260801.
Keterbatasan
- SDK tidak menyertakan browser, desktop, driver siap pakai, atau kebijakan URL: Anda yang menyediakannya. Lihat Integrasi mitra, Mengimplementasikan driver, dan Menulis kebijakan URL.
- Kebijakan URL hanya melihat panggilan
navigate: Kebijakan ini tidak melihat permintaan yang dibuat oleh halaman, atau halaman yang terbuka dengan cara lain, seperti dari tautan yang diklik Claude atau redirect. Lihat Mengintersep permintaan di driver. - Persetujuan didasarkan pada laporan state terakhir: Halaman dapat berubah setelah laporan tersebut. SDK tidak memeriksa ulang halaman sebelum panggilan berjalan.
- Panggilan pada satu toolset berjalan satu per satu: Anda tidak dapat menonaktifkan perilaku ini.
- SDK tidak memeriksa apakah ID tab unik atau berapa banyak tab yang ada, dan tidak selalu memeriksa bahwa satu tab aktif: API menolak laporan yang melanggar aturan tersebut.
- Untuk panggilan komputer,
confirmmenerima alat beserta inputnya, bukan layarnya: Anda tidak dapat mengetahui dari keduanya apa yang dilakukan klik pada titik tertentu. - SDK tidak mengubah ukuran gambar computer toolset: API menolak gambar yang melebihi batas model. Lihat Menskalakan koordinat dan mengatur ukuran gambar.
Langkah selanjutnya
Alat anggota, blok browser_state, dan pertimbangan keamanan alat tersebut.
17 alat, koordinat dan batas gambar, serta pertimbangan keamanan alat tersebut.
Cara SDK menjalankan loop, dan cara mengubah pesan yang dikirimnya.
Guardrail untuk aplikasi apa pun yang membaca konten tidak tepercaya.
Was this page helpful?