mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-07 17:09:13 -10:00
feat(app-server): read Fleet profile documents through a bounded effective-roster route
Adds GET /v1/fleet/profiles/{id}/document. Personal/workspace profiles and
selected named Fleet files return their exact bounded pinned text; built-in,
config, plugin and Claude members return an explicitly read-only effective
projection with a null path. The roster now comes from the captured workspace
identity and plugin discovery, and the final workspace-identity check always
runs, even when the read fails. Revision uses sha256_hex over
(id, kind, origin, path, text). Generated schema, TypeScript, SDK types and
EN/ZH docs are updated.
Verified (local, existing checks, source only): 41 passed, 0 failed
(fleet runtime API test, engine fleet::profile cases, protocol schema+wire).
Not verified: no matched native HTTP read/refusal observer, no mounted App
viewer, no full sealed gate; PUT remains unimplemented and writes stay off.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
This commit is contained in:
1 parent
f4978e8fab
commit
a86ef86df4
11 files changed
+338
-24
No files matched your search
@@ -487,6 +487,59 @@
|
||||
],
|
||||
"description": "Fixed capability map advertised by `GET /v1/runtime/info`.\n\nAll fields are required on serialization so clients can rely on the shape."
|
||||
},
|
||||
"FleetProfileDocument": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"kind": {
|
||||
"$ref": "#/$defs/FleetProfileDocumentKind"
|
||||
},
|
||||
"origin": {
|
||||
"type": "string"
|
||||
},
|
||||
"path": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
},
|
||||
"text": {
|
||||
"type": "string"
|
||||
},
|
||||
"revision": {
|
||||
"type": "string"
|
||||
},
|
||||
"writable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"refusal_reason": {
|
||||
"type": [
|
||||
"string",
|
||||
"null"
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"kind",
|
||||
"origin",
|
||||
"path",
|
||||
"text",
|
||||
"revision",
|
||||
"writable",
|
||||
"refusal_reason"
|
||||
]
|
||||
},
|
||||
"FleetProfileDocumentKind": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"profile",
|
||||
"fleet"
|
||||
]
|
||||
},
|
||||
"RuntimeExperimentalCapabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
||||
@@ -39,6 +39,19 @@ export type DynamicToolSpecOutput = {
|
||||
[key: string]: Json | undefined
|
||||
}
|
||||
|
||||
export type FleetProfileDocument = {
|
||||
id: string
|
||||
kind: FleetProfileDocumentKind
|
||||
origin: string
|
||||
path: (string | null)
|
||||
text: string
|
||||
revision: string
|
||||
writable: boolean
|
||||
refusal_reason: (string | null)
|
||||
}
|
||||
|
||||
export type FleetProfileDocumentKind = 'profile' | 'fleet'
|
||||
|
||||
export type RuntimeCapabilities = {
|
||||
client_token_intents: boolean
|
||||
account_session: boolean
|
||||
|
||||
@@ -47,6 +47,7 @@ pub fn runtime_schema() -> Value {
|
||||
let _ = output.subschema_for::<ThreadLoadedListResponse>();
|
||||
let _ = output.subschema_for::<ThreadSetNameResponse>();
|
||||
let _ = output.subschema_for::<RuntimeCapabilities>();
|
||||
let _ = output.subschema_for::<FleetProfileDocument>();
|
||||
let _ = output.subschema_for::<RuntimeExperimentalCapabilities>();
|
||||
let _ = output.subschema_for::<DynamicToolCallParams>();
|
||||
let _ = output.subschema_for::<DynamicToolSpec>();
|
||||
|
||||
@@ -9,6 +9,28 @@ pub mod watch_event;
|
||||
pub const RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION: u32 = 1;
|
||||
pub const RUNTIME_API_VERSION: &str = "1.0";
|
||||
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum FleetProfileDocumentKind {
|
||||
Profile,
|
||||
Fleet,
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct FleetProfileDocument {
|
||||
pub id: String,
|
||||
pub kind: FleetProfileDocumentKind,
|
||||
pub origin: String,
|
||||
pub path: Option<String>,
|
||||
pub text: String,
|
||||
pub revision: String,
|
||||
pub writable: bool,
|
||||
pub refusal_reason: Option<String>,
|
||||
}
|
||||
|
||||
/// Maximum JSON input (including base64 expansion) for an image turn.
|
||||
pub const MAX_RUNTIME_IMAGE_BODY_BYTES: usize = 8 * 1024 * 1024;
|
||||
pub const MAX_RUNTIME_IMAGES: usize = 10;
|
||||
|
||||
@@ -25,8 +25,9 @@ use base64::Engine as _;
|
||||
use base64::engine::general_purpose::URL_SAFE_NO_PAD;
|
||||
use chrono::Utc;
|
||||
use codewhale_app_server_protocol::runtime::{
|
||||
DynamicToolCallResult, RUNTIME_API_VERSION, RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION,
|
||||
RuntimeCapabilities, RuntimeEventEnvelope, RuntimeExperimentalCapabilities,
|
||||
DynamicToolCallResult, FleetProfileDocument, FleetProfileDocumentKind, RUNTIME_API_VERSION,
|
||||
RUNTIME_EVENT_ENVELOPE_SCHEMA_VERSION, RuntimeCapabilities, RuntimeEventEnvelope,
|
||||
RuntimeExperimentalCapabilities,
|
||||
};
|
||||
#[cfg(unix)]
|
||||
use codewhale_paths::daemon_socket::default_socket_path;
|
||||
@@ -2917,6 +2918,10 @@ pub fn build_router(state: RuntimeApiState) -> Router {
|
||||
.route("/v1/agent-runs/{run_id}", get(get_agent_run))
|
||||
.route("/v1/agent-runs/{run_id}/cancel", post(cancel_agent_run))
|
||||
.route("/v1/fleet/profiles", get(list_fleet_profiles))
|
||||
.route(
|
||||
"/v1/fleet/profiles/{id}/document",
|
||||
get(get_fleet_profile_document),
|
||||
)
|
||||
.route(
|
||||
"/v1/fleet/runs",
|
||||
get(list_fleet_runs).post(create_fleet_run),
|
||||
@@ -4284,30 +4289,140 @@ async fn cancel_agent_run(
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_fleet_profiles<T: Send + 'static>(
|
||||
state: RuntimeApiState,
|
||||
read: impl FnOnce(
|
||||
&RuntimeApiState,
|
||||
&codewhale_engine::fleet::roster::FleetRoster,
|
||||
) -> Result<T, ApiError>
|
||||
+ Send
|
||||
+ 'static,
|
||||
) -> Result<T, ApiError> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let env_ticket = codewhale_engine::test_support::env_scope_ticket();
|
||||
tokio::task::spawn_blocking(move || {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
let _membership = codewhale_engine::test_support::join_env_scope(env_ticket);
|
||||
state
|
||||
.workspace_scope
|
||||
.validate_sync()
|
||||
.map_err(|_| ApiError::conflict("selected workspace identity changed"))?;
|
||||
let plugins = state
|
||||
.plugin_discovery
|
||||
.registry_for_workspace(&state.workspace);
|
||||
let roster = codewhale_engine::fleet::identity::load_effective_roster(
|
||||
&state.config.read().fleet_config(),
|
||||
&state.workspace,
|
||||
Some(plugins.as_ref()),
|
||||
);
|
||||
let result = read(&state, &roster);
|
||||
state
|
||||
.workspace_scope
|
||||
.validate_sync()
|
||||
.map_err(|_| ApiError::conflict("selected workspace identity changed"))?;
|
||||
result
|
||||
})
|
||||
.await
|
||||
.map_err(|_| ApiError::internal("Fleet profiles could not be read"))?
|
||||
}
|
||||
|
||||
async fn list_fleet_profiles(
|
||||
State(state): State<RuntimeApiState>,
|
||||
) -> Result<Json<Value>, ApiError> {
|
||||
let manager = open_fleet_manager(&state)?;
|
||||
// Same roster path the manager uses to validate `agent_profile` ids on
|
||||
// run creation, so GUI pickers can never offer a profile the runtime
|
||||
// would reject.
|
||||
let roster = manager.agent_roster();
|
||||
let profiles = roster
|
||||
.members()
|
||||
.iter()
|
||||
.map(|member| {
|
||||
json!({
|
||||
"id": member.id.clone(),
|
||||
"display_name": member.display_name.clone(),
|
||||
"description": member.description.clone(),
|
||||
"origin": member.origin.to_string(),
|
||||
read_fleet_profiles(state, |_, roster| {
|
||||
let profiles = roster
|
||||
.members()
|
||||
.iter()
|
||||
.map(|member| {
|
||||
json!({
|
||||
"id": member.id,
|
||||
"display_name": member.display_name,
|
||||
"description": member.description,
|
||||
"origin": member.origin.to_string(),
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Json(json!({
|
||||
"profiles": profiles,
|
||||
"load_error": roster.load_error().map(str::to_string),
|
||||
})))
|
||||
.collect::<Vec<_>>();
|
||||
Ok(Json(
|
||||
json!({"profiles": profiles, "load_error": roster.load_error()}),
|
||||
))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_fleet_profile_document(
|
||||
State(state): State<RuntimeApiState>,
|
||||
Path(id): Path<String>,
|
||||
) -> Result<Json<FleetProfileDocument>, ApiError> {
|
||||
if id.len() > 160 {
|
||||
return Err(ApiError::bad_request("Profile identity is too long"));
|
||||
}
|
||||
read_fleet_profiles(state, move |state, roster| {
|
||||
use codewhale_engine::fleet::profile::{
|
||||
FleetProfileScope, MAX_PROFILE_DOCUMENT_BYTES, agent_profile_dir_for_scope,
|
||||
parse_agent_profile_text, read_agent_profile_source_document,
|
||||
};
|
||||
use codewhale_engine::fleet::roster::ProfileOrigin;
|
||||
if let Some(error) = roster.load_error() {
|
||||
return Err(ApiError::conflict(error));
|
||||
}
|
||||
let member = roster.get(&id).filter(|member| member.id == id)
|
||||
.ok_or_else(|| ApiError::not_found("Profile is not in the effective roster"))?;
|
||||
let (kind, path, text, reason) = if matches!(member.origin, ProfileOrigin::Workspace | ProfileOrigin::Personal) {
|
||||
let scope = if member.origin == ProfileOrigin::Workspace { FleetProfileScope::Project } else { FleetProfileScope::Personal };
|
||||
let text = read_agent_profile_source_document(scope, &state.workspace, &member.source)
|
||||
.map_err(|error| ApiError::conflict(error.to_string()))?
|
||||
.ok_or_else(|| ApiError::conflict("Profile source disappeared; read the roster again"))?;
|
||||
let path = member.source.to_str().ok_or_else(|| ApiError::conflict("Profile source path is not UTF-8"))?.to_owned();
|
||||
let directory = agent_profile_dir_for_scope(scope, &state.workspace)
|
||||
.map_err(|error| ApiError::conflict(error.to_string()))?;
|
||||
let kind = if member.source.parent() == Some(directory.as_path()) {
|
||||
let parsed = parse_agent_profile_text(&member.source, &text)
|
||||
.map_err(|error| ApiError::conflict(error.to_string()))?;
|
||||
if parsed.id != id {
|
||||
return Err(ApiError::conflict("Profile identity changed; read the roster again"));
|
||||
}
|
||||
FleetProfileDocumentKind::Profile
|
||||
} else {
|
||||
let fleet = codewhale_engine::fleet::store::FleetFile::parse(&text)
|
||||
.map_err(|error| ApiError::conflict(error.to_string()))?;
|
||||
if fleet.member(&id).is_none() {
|
||||
return Err(ApiError::conflict("Fleet member changed; read the roster again"));
|
||||
}
|
||||
FleetProfileDocumentKind::Fleet
|
||||
};
|
||||
(kind, Some(path), text, "Edit this profile or Fleet in the terminal.".to_owned())
|
||||
} else {
|
||||
if let Some(authority) = &member.plugin_authority {
|
||||
codewhale_plugins::registry::verify_plugin_authority(authority)
|
||||
.map_err(ApiError::conflict)?;
|
||||
}
|
||||
if member.profile.role.instructions.as_ref().map_or(0, String::len)
|
||||
.saturating_add(member.profile.role.description.as_ref().map_or(0, String::len))
|
||||
> MAX_PROFILE_DOCUMENT_BYTES
|
||||
{
|
||||
return Err(ApiError::payload_too_large("Effective profile exceeds the one MiB reading limit"));
|
||||
}
|
||||
let text = toml::to_string_pretty(&member.profile)
|
||||
.map_err(|_| ApiError::internal("Effective profile could not be rendered"))?;
|
||||
let reason = match member.origin {
|
||||
ProfileOrigin::BuiltIn => "Effective built-in profile; save a personal profile in the terminal to customize this role.",
|
||||
ProfileOrigin::Config => "Effective configuration profile; edit its defining configuration.",
|
||||
ProfileOrigin::Plugin => "Effective plugin profile; edit its reviewed plugin source.",
|
||||
ProfileOrigin::ClaudeCode => "Effective Codewhale profile from Claude Code; edit its original agent file.",
|
||||
_ => return Err(ApiError::internal("Profile origin changed while reading")),
|
||||
};
|
||||
(FleetProfileDocumentKind::Profile, None, text, reason.to_owned())
|
||||
};
|
||||
if text.len() > MAX_PROFILE_DOCUMENT_BYTES {
|
||||
return Err(ApiError::payload_too_large("Profile document exceeds the one MiB editing limit"));
|
||||
}
|
||||
let origin = member.origin.to_string();
|
||||
let revision_bytes = serde_json::to_vec(&(&id, kind, &origin, &path, &text))
|
||||
.map_err(|_| ApiError::internal("Profile revision could not be rendered"))?;
|
||||
let revision = format!("profile-document-v1-{}", codewhale_runtime::hashing::sha256_hex(revision_bytes));
|
||||
Ok(Json(FleetProfileDocument { id, kind, origin, path, text, revision,
|
||||
writable: false, refusal_reason: Some(reason) }))
|
||||
}).await
|
||||
}
|
||||
|
||||
async fn create_fleet_run(
|
||||
|
||||
@@ -2856,6 +2856,34 @@ async fn fleet_status_runtime_api_exposes_state_and_actions() -> Result<()> {
|
||||
.all(|member| member["origin"].is_string()),
|
||||
"every profile must carry its roster origin"
|
||||
);
|
||||
let document: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/fleet/profiles/manager/document"))
|
||||
.send()
|
||||
.await?
|
||||
.error_for_status()?
|
||||
.json()
|
||||
.await?;
|
||||
assert_eq!(document["id"], "manager");
|
||||
assert_eq!(document["kind"], "profile");
|
||||
assert_eq!(document["writable"], false);
|
||||
assert!(document["path"].is_null());
|
||||
assert!(document["text"].as_str().unwrap().contains("[role]"));
|
||||
assert!(
|
||||
document["revision"]
|
||||
.as_str()
|
||||
.unwrap()
|
||||
.starts_with("profile-document-v1-")
|
||||
);
|
||||
assert_eq!(
|
||||
client
|
||||
.get(format!(
|
||||
"http://{addr}/v1/fleet/profiles/not-a-member/document"
|
||||
))
|
||||
.send()
|
||||
.await?
|
||||
.status(),
|
||||
reqwest::StatusCode::NOT_FOUND
|
||||
);
|
||||
|
||||
let worker: serde_json::Value = client
|
||||
.get(format!("http://{addr}/v1/fleet/workers/{worker_id}"))
|
||||
|
||||
@@ -146,7 +146,11 @@ pub fn read_agent_profile_document(
|
||||
path: &Path,
|
||||
) -> Result<Option<String>> {
|
||||
let (root, relative) = profile_document_location(scope, workspace, path)?;
|
||||
let file = match codewhale_util::files::WorkspaceFile::open(&root, &relative, false) {
|
||||
read_profile_document_at(&root, &relative)
|
||||
}
|
||||
|
||||
fn read_profile_document_at(root: &Path, relative: &Path) -> Result<Option<String>> {
|
||||
let file = match codewhale_util::files::WorkspaceFile::open(root, relative, false) {
|
||||
Ok(file) => file,
|
||||
Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(error) => return Err(error.into()),
|
||||
@@ -154,6 +158,40 @@ pub fn read_agent_profile_document(
|
||||
read_profile_document_file(&file)
|
||||
}
|
||||
|
||||
pub fn read_agent_profile_source_document(
|
||||
scope: FleetProfileScope,
|
||||
workspace: &Path,
|
||||
path: &Path,
|
||||
) -> Result<Option<String>> {
|
||||
let directory = agent_profile_dir_for_scope(scope, workspace)?;
|
||||
if path.parent() == Some(directory.as_path()) {
|
||||
return read_agent_profile_document(scope, workspace, path);
|
||||
}
|
||||
let root = match scope {
|
||||
FleetProfileScope::Project => workspace.to_path_buf(),
|
||||
FleetProfileScope::Personal => directory
|
||||
.parent()
|
||||
.context("Personal profile directory has no state root")?
|
||||
.to_path_buf(),
|
||||
};
|
||||
let fleet_directory = match scope {
|
||||
FleetProfileScope::Project => root.join(".codewhale").join(super::store::FLEET_DIR),
|
||||
FleetProfileScope::Personal => root.join(super::store::FLEET_DIR),
|
||||
};
|
||||
anyhow::ensure!(
|
||||
root.is_absolute()
|
||||
&& path.parent() == Some(fleet_directory.as_path())
|
||||
&& path.extension().and_then(|value| value.to_str()) == Some("toml"),
|
||||
"Profile source is outside its selected directory"
|
||||
);
|
||||
let relative = path.strip_prefix(&root)?;
|
||||
anyhow::ensure!(
|
||||
codewhale_util::files::path_is_confined(relative),
|
||||
"Profile source is outside its selected directory"
|
||||
);
|
||||
read_profile_document_at(&root, relative)
|
||||
}
|
||||
|
||||
pub fn save_agent_profile_document(
|
||||
scope: FleetProfileScope,
|
||||
workspace: &Path,
|
||||
|
||||
@@ -3206,6 +3206,23 @@ allowed. Added in v0.8.10 (#561), tightened in v0.9.1 (#4454).
|
||||
|
||||
## Managed Fleet Runtime and SDK helpers
|
||||
|
||||
`GET /v1/fleet/profiles` reads the captured workspace's effective roster,
|
||||
including its selected Fleet and admitted plugins, without opening a run ledger.
|
||||
`GET /v1/fleet/profiles/{id}/document` addresses an exact effective member ID.
|
||||
Its shared `FleetProfileDocument` contract returns `id`, `kind` (`profile` or
|
||||
`fleet`), `origin`, nullable `path`, `text`, opaque source-bound `revision`,
|
||||
`writable` and nullable `refusal_reason`. Personal/project profile documents and
|
||||
selected Fleet files return exact UTF-8 bytes through a bounded, pinned reader
|
||||
(one MiB maximum). Changed identity, missing source or linked paths are refused.
|
||||
Built-in, config-backed and foreign-managed members instead return an explicitly
|
||||
read-only effective profile projection with no file path; sentinel paths and
|
||||
native preset entry identifiers are never opened as documents.
|
||||
|
||||
All documents currently advertise `writable: false`. No document PUT is exposed;
|
||||
use the terminal or the defining source to edit. A revision is not write
|
||||
permission or a live-roster adoption receipt. Watch clients may read these
|
||||
resources under the existing workspace and authentication boundaries.
|
||||
|
||||
The Runtime SDK lives in `npm/runtime-sdk` and is exposed as
|
||||
the `@codewhale/runtime-sdk` workspace package. It is deliberately thin: every
|
||||
helper calls the local Rust Runtime API and therefore cannot bypass Codewhale's
|
||||
|
||||
@@ -2565,6 +2565,20 @@ Fleet 流(`/v1/fleet/runs/{run_id}/events`)保留自己的结束帧,
|
||||
|
||||
## 托管 Fleet 运行时与 SDK 助手
|
||||
|
||||
`GET /v1/fleet/profiles` 从已捕获的工作区读取实际生效的成员列表,包含选中的
|
||||
Fleet 和已获准的插件,不会为此打开运行账本。
|
||||
`GET /v1/fleet/profiles/{id}/document` 使用精确的生效成员 ID。共享的
|
||||
`FleetProfileDocument` 契约返回 `id`、`kind`(`profile` 或 `fleet`)、`origin`、
|
||||
可空的 `path`、`text`、绑定源身份的不透明 `revision`、`writable` 和可空的
|
||||
`refusal_reason`。个人/项目配置及选中的 Fleet 文件通过固定目录句柄读取原始
|
||||
UTF-8 字节,上限为一 MiB。身份改变、源文件消失或链接路径会被拒绝。
|
||||
内置、配置文件和外部管理的成员只返回明确只读的生效配置视图,不提供文件路径;
|
||||
不会把占位路径或原生预设入口标识当作配置文件打开。
|
||||
|
||||
所有文档目前均返回 `writable: false`,尚未提供文档 PUT。请在终端或定义该成员的
|
||||
源中编辑。修订号不代表写入权限,也不代表运行中的成员列表已采用更改。只读客户端
|
||||
可在现有工作区和身份验证边界内读取这些资源。
|
||||
|
||||
运行时 SDK 位于 `npm/runtime-sdk`,并作为
|
||||
`@codewhale/runtime-sdk` 工作区包对外暴露。它有意保持轻薄:每个
|
||||
助手都只是调用本地 Rust 运行时 API,因此无法绕过 Codewhale 的
|
||||
|
||||
+13
@@ -39,6 +39,19 @@ export type DynamicToolSpecOutput = {
|
||||
[key: string]: Json | undefined
|
||||
}
|
||||
|
||||
export type FleetProfileDocument = {
|
||||
id: string
|
||||
kind: FleetProfileDocumentKind
|
||||
origin: string
|
||||
path: (string | null)
|
||||
text: string
|
||||
revision: string
|
||||
writable: boolean
|
||||
refusal_reason: (string | null)
|
||||
}
|
||||
|
||||
export type FleetProfileDocumentKind = 'profile' | 'fleet'
|
||||
|
||||
export type RuntimeCapabilities = {
|
||||
client_token_intents: boolean
|
||||
account_session: boolean
|
||||
|
||||
Vendored
+1
-1
@@ -1,5 +1,5 @@
|
||||
import type { RuntimeEventEnvelope, DynamicToolCallParams, DynamicToolCallResult } from "./generated/runtime.js";
|
||||
export type { ThreadLoadedListParams, ThreadLoadedListResponse, ThreadSetNameParams, ThreadSetNameResponse, RuntimeImageInput, RuntimeEventEnvelope, RuntimeCapabilities, RuntimeExperimentalCapabilities, DynamicToolSpec, DynamicToolSpecOutput, DynamicToolItemStatus, DynamicToolCallParams, DynamicToolCallResult, DynamicToolCallContent, TurnEnvironmentParams } from "./generated/runtime.js";
|
||||
export type { FleetProfileDocument, FleetProfileDocumentKind, ThreadLoadedListParams, ThreadLoadedListResponse, ThreadSetNameParams, ThreadSetNameResponse, RuntimeImageInput, RuntimeEventEnvelope, RuntimeCapabilities, RuntimeExperimentalCapabilities, DynamicToolSpec, DynamicToolSpecOutput, DynamicToolItemStatus, DynamicToolCallParams, DynamicToolCallResult, DynamicToolCallContent, TurnEnvironmentParams } from "./generated/runtime.js";
|
||||
|
||||
export type FleetRunId = string;
|
||||
export type FleetRunStatus =
|
||||
|
||||
Reference in new issue
Block a user