From d072d142ca88c537e23de0bfc5cc4159924646f6 Mon Sep 17 00:00:00 2001 From: github-ops Date: Fri, 19 Jun 2026 21:15:02 +0000 Subject: [PATCH] Polish community UI copy and cleanup --- ...46db6f0f7d17e6867f929c44027990fb94b57.json | 94 -- ...940b65d2167dada6b6110985ce0bd95480b93.json | 99 -- ...dc8a6d4068e33a5509f9e314dd0d97eac178b.json | 98 -- ...a53d8a109b8aa766ab1c2b11f9b52e250adc8.json | 94 -- apps/admin-api/src/app.rs | 229 +-- apps/admin-api/src/error.rs | 13 - apps/admin-api/src/main.rs | 3 - apps/admin-api/src/service.rs | 396 +---- apps/mcp-server/src/main.rs | 5 - apps/ui/html/agents.html | 15 +- apps/ui/html/api-keys.html | 38 +- apps/ui/html/logs.html | 5 +- apps/ui/html/secrets.html | 17 +- apps/ui/html/settings.html | 52 +- apps/ui/html/usage.html | 3 - apps/ui/html/wizard/index.html | 13 +- apps/ui/html/wizard/step1.html | 12 +- apps/ui/html/wizard/step2.html | 92 +- apps/ui/html/wizard/step3-rest.html | 26 +- apps/ui/html/wizard/step4.html | 48 +- apps/ui/html/wizard/step5.html | 109 +- apps/ui/html/workspace-setup.html | 2 +- apps/ui/index.html | 36 +- apps/ui/js/api-keys.js | 48 - apps/ui/js/api.js | 3 + apps/ui/js/catalog.js | 23 +- apps/ui/js/i18n.js | 712 ++++----- apps/ui/js/settings.js | 42 - apps/ui/js/wizard-upstreams.js | 20 + apps/ui/js/wizard.js | 7 +- apps/ui/tests/e2e/workspace-settings.spec.js | 5 +- crates/crank-adapter-rest/src/client.rs | 180 +-- crates/crank-adapter-rest/src/lib.rs | 69 +- crates/crank-adapter-rest/src/model.rs | 17 - crates/crank-adapter-rest/src/sse.rs | 160 -- crates/crank-community-mcp/src/app.rs | 1307 +---------------- crates/crank-core/src/ext/protocol.rs | 117 +- crates/crank-core/src/ids.rs | 2 - crates/crank-core/src/lib.rs | 31 +- crates/crank-core/src/operation.rs | 331 +---- crates/crank-core/src/protocol.rs | 83 -- crates/crank-core/src/soap.rs | 101 -- crates/crank-core/src/stream_session.rs | 318 ---- crates/crank-core/src/streaming.rs | 364 ----- crates/crank-registry/src/lib.rs | 16 +- crates/crank-registry/src/migrations.rs | 72 - crates/crank-registry/src/model.rs | 75 +- crates/crank-registry/src/postgres/mod.rs | 484 +----- crates/crank-registry/src/postgres/stream.rs | 634 -------- crates/crank-runtime/src/aggregation.rs | 292 ---- crates/crank-runtime/src/error.rs | 12 - crates/crank-runtime/src/executor.rs | 271 +--- crates/crank-runtime/src/lib.rs | 4 - crates/crank-runtime/src/limits.rs | 24 - crates/crank-runtime/src/model.rs | 10 +- crates/crank-runtime/src/redaction.rs | 124 -- crates/crank-runtime/src/streaming.rs | 26 - 57 files changed, 710 insertions(+), 6773 deletions(-) delete mode 100644 .sqlx/query-a1d2c9b16b61d226701449fc22146db6f0f7d17e6867f929c44027990fb94b57.json delete mode 100644 .sqlx/query-b68377d8ffd5bdc9b8e417170f4940b65d2167dada6b6110985ce0bd95480b93.json delete mode 100644 .sqlx/query-e86682005480df007b488b8543adc8a6d4068e33a5509f9e314dd0d97eac178b.json delete mode 100644 .sqlx/query-f551a9cd253b7fd0dc9a387a141a53d8a109b8aa766ab1c2b11f9b52e250adc8.json delete mode 100644 crates/crank-adapter-rest/src/sse.rs delete mode 100644 crates/crank-core/src/soap.rs delete mode 100644 crates/crank-core/src/stream_session.rs delete mode 100644 crates/crank-core/src/streaming.rs delete mode 100644 crates/crank-registry/src/postgres/stream.rs delete mode 100644 crates/crank-runtime/src/aggregation.rs delete mode 100644 crates/crank-runtime/src/redaction.rs delete mode 100644 crates/crank-runtime/src/streaming.rs diff --git a/.sqlx/query-a1d2c9b16b61d226701449fc22146db6f0f7d17e6867f929c44027990fb94b57.json b/.sqlx/query-a1d2c9b16b61d226701449fc22146db6f0f7d17e6867f929c44027990fb94b57.json deleted file mode 100644 index 8900459..0000000 --- a/.sqlx/query-a1d2c9b16b61d226701449fc22146db6f0f7d17e6867f929c44027990fb94b57.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n protocol,\n mode,\n status,\n cursor_json,\n state_json,\n expires_at as \"expires_at!: OffsetDateTime\",\n last_poll_at as \"last_poll_at: OffsetDateTime\",\n created_at as \"created_at!: OffsetDateTime\",\n closed_at as \"closed_at: OffsetDateTime\"\n from stream_sessions\n where id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "workspace_id", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "agent_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "operation_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "protocol", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "mode", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "status", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "cursor_json", - "type_info": "Jsonb" - }, - { - "ordinal": 8, - "name": "state_json", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "expires_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "last_poll_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "created_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "closed_at: OffsetDateTime", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - false, - true, - false, - false, - true, - false, - true - ] - }, - "hash": "a1d2c9b16b61d226701449fc22146db6f0f7d17e6867f929c44027990fb94b57" -} diff --git a/.sqlx/query-b68377d8ffd5bdc9b8e417170f4940b65d2167dada6b6110985ce0bd95480b93.json b/.sqlx/query-b68377d8ffd5bdc9b8e417170f4940b65d2167dada6b6110985ce0bd95480b93.json deleted file mode 100644 index 21fc532..0000000 --- a/.sqlx/query-b68377d8ffd5bdc9b8e417170f4940b65d2167dada6b6110985ce0bd95480b93.json +++ /dev/null @@ -1,99 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n protocol,\n mode,\n status,\n cursor_json,\n state_json,\n expires_at as \"expires_at!: OffsetDateTime\",\n last_poll_at as \"last_poll_at: OffsetDateTime\",\n created_at as \"created_at!: OffsetDateTime\",\n closed_at as \"closed_at: OffsetDateTime\"\n from stream_sessions\n where workspace_id = $1\n and ($2::text is null or agent_id = $2)\n and ($3::text is null or operation_id = $3)\n and ($4::text is null or status = $4)\n and ($5::text is null or mode = $5)\n order by created_at desc\n limit $6", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "workspace_id", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "agent_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "operation_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "protocol", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "mode", - "type_info": "Text" - }, - { - "ordinal": 6, - "name": "status", - "type_info": "Text" - }, - { - "ordinal": 7, - "name": "cursor_json", - "type_info": "Jsonb" - }, - { - "ordinal": 8, - "name": "state_json", - "type_info": "Jsonb" - }, - { - "ordinal": 9, - "name": "expires_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "last_poll_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "created_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "closed_at: OffsetDateTime", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Text", - "Text", - "Int8" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - false, - true, - false, - false, - true, - false, - true - ] - }, - "hash": "b68377d8ffd5bdc9b8e417170f4940b65d2167dada6b6110985ce0bd95480b93" -} diff --git a/.sqlx/query-e86682005480df007b488b8543adc8a6d4068e33a5509f9e314dd0d97eac178b.json b/.sqlx/query-e86682005480df007b488b8543adc8a6d4068e33a5509f9e314dd0d97eac178b.json deleted file mode 100644 index 19f81b5..0000000 --- a/.sqlx/query-e86682005480df007b488b8543adc8a6d4068e33a5509f9e314dd0d97eac178b.json +++ /dev/null @@ -1,98 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n status,\n progress_json,\n result_json,\n error_json,\n expires_at as \"expires_at: OffsetDateTime\",\n last_poll_at as \"last_poll_at: OffsetDateTime\",\n created_at as \"created_at!: OffsetDateTime\",\n updated_at as \"updated_at!: OffsetDateTime\",\n finished_at as \"finished_at: OffsetDateTime\"\n from async_jobs\n where workspace_id = $1\n and ($2::text is null or agent_id = $2)\n and ($3::text is null or operation_id = $3)\n and ($4::text is null or status = $4)\n order by updated_at desc\n limit $5", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "workspace_id", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "agent_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "operation_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "status", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "progress_json", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "result_json", - "type_info": "Jsonb" - }, - { - "ordinal": 7, - "name": "error_json", - "type_info": "Jsonb" - }, - { - "ordinal": 8, - "name": "expires_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "last_poll_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "created_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "updated_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "finished_at: OffsetDateTime", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text", - "Text", - "Text", - "Text", - "Int8" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - true, - true, - true, - true, - false, - false, - true - ] - }, - "hash": "e86682005480df007b488b8543adc8a6d4068e33a5509f9e314dd0d97eac178b" -} diff --git a/.sqlx/query-f551a9cd253b7fd0dc9a387a141a53d8a109b8aa766ab1c2b11f9b52e250adc8.json b/.sqlx/query-f551a9cd253b7fd0dc9a387a141a53d8a109b8aa766ab1c2b11f9b52e250adc8.json deleted file mode 100644 index 3ff8cb4..0000000 --- a/.sqlx/query-f551a9cd253b7fd0dc9a387a141a53d8a109b8aa766ab1c2b11f9b52e250adc8.json +++ /dev/null @@ -1,94 +0,0 @@ -{ - "db_name": "PostgreSQL", - "query": "select\n id,\n workspace_id,\n agent_id,\n operation_id,\n status,\n progress_json,\n result_json,\n error_json,\n expires_at as \"expires_at: OffsetDateTime\",\n last_poll_at as \"last_poll_at: OffsetDateTime\",\n created_at as \"created_at!: OffsetDateTime\",\n updated_at as \"updated_at!: OffsetDateTime\",\n finished_at as \"finished_at: OffsetDateTime\"\n from async_jobs\n where id = $1", - "describe": { - "columns": [ - { - "ordinal": 0, - "name": "id", - "type_info": "Text" - }, - { - "ordinal": 1, - "name": "workspace_id", - "type_info": "Text" - }, - { - "ordinal": 2, - "name": "agent_id", - "type_info": "Text" - }, - { - "ordinal": 3, - "name": "operation_id", - "type_info": "Text" - }, - { - "ordinal": 4, - "name": "status", - "type_info": "Text" - }, - { - "ordinal": 5, - "name": "progress_json", - "type_info": "Jsonb" - }, - { - "ordinal": 6, - "name": "result_json", - "type_info": "Jsonb" - }, - { - "ordinal": 7, - "name": "error_json", - "type_info": "Jsonb" - }, - { - "ordinal": 8, - "name": "expires_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 9, - "name": "last_poll_at: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 10, - "name": "created_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 11, - "name": "updated_at!: OffsetDateTime", - "type_info": "Timestamptz" - }, - { - "ordinal": 12, - "name": "finished_at: OffsetDateTime", - "type_info": "Timestamptz" - } - ], - "parameters": { - "Left": [ - "Text" - ] - }, - "nullable": [ - false, - false, - true, - false, - false, - false, - true, - true, - true, - true, - false, - false, - true - ] - }, - "hash": "f551a9cd253b7fd0dc9a387a141a53d8a109b8aa766ab1c2b11f9b52e250adc8" -} diff --git a/apps/admin-api/src/app.rs b/apps/admin-api/src/app.rs index edd01ce..0b06a4a 100644 --- a/apps/admin-api/src/app.rs +++ b/apps/admin-api/src/app.rs @@ -196,9 +196,8 @@ mod tests { StreamSession, StreamStatus, TransportBehavior, }; use crank_core::{ - ExecutionConfig, GraphqlOperationType, GraphqlTarget, HttpMethod, MembershipRole, - OperationSecurityLevel, Protocol, ResponseCachePolicy, RestTarget, SecretKind, Target, - ToolDescription, WebsocketTarget, WorkspaceId, + ExecutionConfig, HttpMethod, MembershipRole, OperationSecurityLevel, Protocol, + ResponseCachePolicy, RestTarget, SecretKind, Target, ToolDescription, WorkspaceId, }; use crank_core::{IdentityError, IdentityProvider, IdentityProviderKind, LoginOutcome}; use crank_mapping::{MappingRule, MappingSet}; @@ -1242,78 +1241,6 @@ mod tests { ); } } - - #[tokio::test(flavor = "multi_thread")] - #[serial] - async fn rejects_graphql_protocol_for_community_operation_create() { - let registry = test_registry().await; - let storage_root = test_storage_root("community_graphql_reject_create"); - let upstream_base_url = spawn_graphql_server().await; - let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await; - let client = authorized_client(&base_url).await; - - let response = client - .post(format!("{base_url}/operations")) - .json(&test_graphql_operation_payload( - &upstream_base_url, - "crm_graphql_not_in_community", - )) - .send() - .await - .unwrap(); - let status = response.status(); - let body = response.json::().await.unwrap(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert_eq!(body["error"]["code"], "validation_error"); - assert_eq!( - body["error"]["message"], - "protocol graphql is not supported in Community" - ); - assert_eq!( - body["error"]["context"], - json!({ - "protocol": "graphql", - "edition": "community", - }) - ); - } - - #[tokio::test(flavor = "multi_thread")] - #[serial] - async fn rejects_unsupported_protocol_for_community_operation_create() { - let registry = test_registry().await; - let storage_root = test_storage_root("community_protocol_reject"); - let base_url = spawn_admin_api(build_test_app(registry, storage_root)).await; - let client = authorized_client(&base_url).await; - - let response = client - .post(format!("{base_url}/operations")) - .json(&test_websocket_operation_payload( - "wss://example.com/stream", - "telemetry_ws", - )) - .send() - .await - .unwrap(); - let status = response.status(); - let body = response.json::().await.unwrap(); - - assert_eq!(status, reqwest::StatusCode::BAD_REQUEST); - assert_eq!(body["error"]["code"], "validation_error"); - assert_eq!( - body["error"]["message"], - "protocol websocket is not supported in Community" - ); - assert_eq!( - body["error"]["context"], - json!({ - "protocol": "websocket", - "edition": "community", - }) - ); - } - #[tokio::test(flavor = "multi_thread")] #[serial] async fn rejects_premium_security_level_for_community_operation_create() { @@ -3306,18 +3233,6 @@ mod tests { format!("http://{}", address) } - async fn spawn_graphql_server() -> String { - let app = Router::new().route("/", post(graphql_handler)); - let listener = TcpListener::bind("127.0.0.1:0").await.unwrap(); - let address = listener.local_addr().unwrap(); - - tokio::spawn(async move { - axum::serve(listener, app).await.unwrap(); - }); - - format!("http://{}", address) - } - #[cfg(any())] async fn spawn_soap_server() -> String { let app = Router::new().route("/", post(soap_handler)); @@ -3501,24 +3416,6 @@ mod tests { })) } - async fn graphql_handler(Json(payload): Json) -> Json { - let email = payload - .get("variables") - .and_then(|variables| variables.get("email")) - .and_then(Value::as_str) - .unwrap_or_default(); - - Json(json!({ - "data": { - "createLead": { - "id": "lead_123", - "status": "created", - "email": email - } - } - })) - } - #[cfg(any())] async fn soap_handler(body: String) -> (axum::http::StatusCode, String) { assert!(body.contains("user@example.com")); @@ -3647,8 +3544,6 @@ mod tests { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: ToolDescription { title: "Create Lead".to_owned(), @@ -3660,65 +3555,6 @@ mod tests { } } - fn test_graphql_operation_payload(endpoint: &str, name: &str) -> OperationPayload { - OperationPayload { - name: name.to_owned(), - display_name: "Create Lead GraphQL".to_owned(), - category: "sales".to_owned(), - protocol: Protocol::Graphql, - security_level: OperationSecurityLevel::Standard, - target: Target::Graphql(GraphqlTarget { - endpoint: endpoint.to_owned(), - operation_type: GraphqlOperationType::Mutation, - operation_name: "CreateLead".to_owned(), - query_template: - "mutation CreateLead($email: String!) { createLead(email: $email) { id status email } }" - .to_owned(), - response_path: "$.response.body.data.createLead".to_owned(), - }), - input_schema: object_schema("email"), - output_schema: object_schema("id"), - input_mapping: MappingSet { - rules: vec![MappingRule { - source: "$.mcp.email".to_owned(), - target: "$.request.variables.email".to_owned(), - required: true, - default_value: None, - transform: None, - condition: None, - notes: None, - }], - }, - output_mapping: MappingSet { - rules: vec![MappingRule { - source: "$.response.data.id".to_owned(), - target: "$.output.id".to_owned(), - required: true, - default_value: None, - transform: None, - condition: None, - notes: None, - }], - }, - execution_config: ExecutionConfig { - timeout_ms: 1_000, - retry_policy: None, - response_cache: None, - auth_profile_ref: None, - headers: BTreeMap::new(), - protocol_options: None, - streaming: None, - }, - tool_description: ToolDescription { - title: "Create Lead GraphQL".to_owned(), - description: "Creates a CRM lead through GraphQL".to_owned(), - tags: vec!["crm".to_owned(), "graphql".to_owned()], - examples: Vec::new(), - }, - wizard_state: None, - } - } - #[cfg(any())] fn test_grpc_operation_payload(server_addr: &str, name: &str) -> OperationPayload { OperationPayload { @@ -3766,8 +3602,6 @@ mod tests { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: ToolDescription { title: "Unary Echo gRPC".to_owned(), @@ -3930,8 +3764,6 @@ mod tests { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: ToolDescription { title: "Create Lead SOAP".to_owned(), @@ -3943,63 +3775,6 @@ mod tests { } } - fn test_websocket_operation_payload(url: &str, name: &str) -> OperationPayload { - OperationPayload { - name: name.to_owned(), - display_name: "Telemetry WebSocket".to_owned(), - category: "telemetry".to_owned(), - protocol: Protocol::Websocket, - security_level: OperationSecurityLevel::Standard, - target: Target::Websocket(WebsocketTarget { - url: url.to_owned(), - subprotocols: Vec::new(), - subscribe_message_template: Some(json!({ "subscribe": true })), - unsubscribe_message_template: None, - static_headers: BTreeMap::new(), - }), - input_schema: object_schema("channel"), - output_schema: object_schema("event"), - input_mapping: MappingSet { - rules: vec![MappingRule { - source: "$.mcp.channel".to_owned(), - target: "$.request.body.channel".to_owned(), - required: true, - default_value: None, - transform: None, - condition: None, - notes: None, - }], - }, - output_mapping: MappingSet { - rules: vec![MappingRule { - source: "$.response.body.event".to_owned(), - target: "$.output.event".to_owned(), - required: true, - default_value: None, - transform: None, - condition: None, - notes: None, - }], - }, - execution_config: ExecutionConfig { - timeout_ms: 1_000, - retry_policy: None, - response_cache: None, - auth_profile_ref: None, - headers: BTreeMap::new(), - protocol_options: None, - streaming: None, - }, - tool_description: ToolDescription { - title: "Telemetry WebSocket".to_owned(), - description: "Streams telemetry events".to_owned(), - tags: vec!["telemetry".to_owned(), "websocket".to_owned()], - examples: Vec::new(), - }, - wizard_state: None, - } - } - #[cfg(any())] const SOAP_TEST_WSDL: &str = r#" &'static str { match error { RuntimeError::Schema(_) => "runtime_schema_error", RuntimeError::Mapping(_) => "runtime_mapping_error", - RuntimeError::GraphqlAdapter(_) => "runtime_graphql_error", - RuntimeError::GrpcAdapter(_) => "runtime_grpc_error", RuntimeError::RestAdapter(_) => "runtime_rest_error", RuntimeError::ProtocolAdapter(_) => "runtime_adapter_error", - RuntimeError::SoapAdapter(_) => "runtime_soap_error", - RuntimeError::WebsocketAdapter(_) => "runtime_websocket_error", RuntimeError::UnsupportedProtocol { .. } => "runtime_protocol_error", RuntimeError::ConcurrencyLimitExceeded { .. } => "runtime_overloaded", RuntimeError::InvalidPreparedRequest { .. } => "runtime_request_error", - RuntimeError::MissingStreamingConfig { .. } => "runtime_streaming_config_error", RuntimeError::UnsupportedExecutionMode { .. } => "runtime_streaming_mode_error", - RuntimeError::InvalidStreamingPayload { .. } => "runtime_streaming_payload_error", RuntimeError::MissingAuthProfile { .. } => "runtime_auth_profile_error", RuntimeError::MissingSecret { .. } | RuntimeError::MissingSecretVersion { .. } => { "runtime_secret_error" @@ -459,10 +453,6 @@ pub fn runtime_error_context(error: &RuntimeError) -> Option { "field": field, "reason": reason, })), - RuntimeError::InvalidStreamingPayload { field, reason } => Some(json!({ - "field": field, - "reason": reason, - })), RuntimeError::InvalidAuthSecretValue { secret_id, reason } => Some(json!({ "secret_id": secret_id, "reason": reason, @@ -481,9 +471,6 @@ pub fn runtime_error_context(error: &RuntimeError) -> Option { "secret_id": secret_id, "version": version, })), - RuntimeError::MissingStreamingConfig { operation_id } => Some(json!({ - "operation_id": operation_id, - })), RuntimeError::UnsupportedExecutionMode { operation_id, mode } => Some(json!({ "operation_id": operation_id, "mode": mode, diff --git a/apps/admin-api/src/main.rs b/apps/admin-api/src/main.rs index 4339b77..79ed754 100644 --- a/apps/admin-api/src/main.rs +++ b/apps/admin-api/src/main.rs @@ -88,9 +88,6 @@ async fn main() -> Result<(), Box> { info!( runtime_max_concurrent_unary = runtime_limits.max_concurrent_unary, - runtime_max_concurrent_window = runtime_limits.max_concurrent_window, - runtime_max_concurrent_sessions = runtime_limits.max_concurrent_sessions, - runtime_max_concurrent_jobs = runtime_limits.max_concurrent_jobs, admin_rate_limit_rps = api_rate_limit.requests_per_second, admin_rate_limit_burst = api_rate_limit.burst, cache_backend = %cache_config.backend, diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index d08cc33..60c943e 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -4,31 +4,29 @@ use std::sync::Arc; use base64::{Engine as _, engine::general_purpose::URL_SAFE_NO_PAD}; use crank_core::{ - Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AggregationMode, - AsyncJobHandle, AuditSink, AuthConfig, AuthKind, AuthProfile, AuthProfileId, CapabilityProfile, - CommunityCapabilityProfile, ConfigExport, EditionCapabilities, ExecutionMode, ExportMode, - GeneratedDraft, GeneratedDraftStatus, IdentityError, IdentityProvider, InvocationLevel, - InvocationLog, InvocationLogId, InvocationSource, InvocationStatus, JobStatus, LoginOutcome, - MachineTokenIssuer, MembershipRole, NoMachineTokenIssuer, NoopAuditSink, OperationId, - OperationSecurityLevel, OperationStatus, OwnerOnlyPolicyEngine, PlatformApiKey, - PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, PolicyEngine, ProductEdition, - Protocol, ResponseCachePolicy, SampleId, Samples, Secret, SecretId, SecretKind, SecretStatus, - StreamSession, StreamStatus, Target, TransportBehavior, UsagePeriod, UserId, UserSessionId, - WizardState, Workspace, WorkspaceId, WorkspaceStatus, + Agent, AgentId, AgentOperationBinding, AgentStatus, AgentVersion, AuditSink, AuthConfig, + AuthKind, AuthProfile, AuthProfileId, CapabilityProfile, CommunityCapabilityProfile, + ConfigExport, EditionCapabilities, ExecutionMode, ExportMode, GeneratedDraft, + GeneratedDraftStatus, IdentityError, IdentityProvider, InvocationLevel, InvocationLog, + InvocationLogId, InvocationSource, InvocationStatus, LoginOutcome, MachineTokenIssuer, + MembershipRole, NoMachineTokenIssuer, NoopAuditSink, OperationId, OperationSecurityLevel, + OperationStatus, OwnerOnlyPolicyEngine, PlatformApiKey, PlatformApiKeyId, PlatformApiKeyScope, + PlatformApiKeyStatus, PolicyEngine, ProductEdition, Protocol, ResponseCachePolicy, SampleId, + Samples, Secret, SecretId, SecretKind, SecretStatus, Target, UsagePeriod, UserId, + UserSessionId, WizardState, Workspace, WorkspaceId, WorkspaceStatus, }; use crank_mapping::{JsonPathRoot, MappingSet, infer_mapping_from_samples}; use crank_registry::{ AgentSummary, AgentVersionRecord, CreateAgentDraftVersionRequest, CreateAgentRequest, - CreateAsyncJobRequest, CreateInvocationLogRequest, CreatePlatformApiKeyRequest, - CreateSecretRequest, CreateStreamSessionRequest, CreateVersionRequest, CreateWorkspaceRequest, - InvocationLogRecord, ListInvocationLogsQuery, OperationAgentRef, OperationSampleMetadata, - OperationSummary, OperationUsageSummary, OperationVersionRecord, PlatformApiKeyRecord, - PostgresRegistry, PublishAgentRequest, PublishRequest, RegistryError, RegistryOperation, - RotateSecretRequest, SampleKind, SaveAgentBindingsRequest, SaveAuthProfileRequest, - SaveSampleMetadataRequest, SaveWorkspaceUpstreamRequest, UpdateAsyncJobStatusRequest, - UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, UsageQuery, - UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord, - WorkspaceUpstream, WorkspaceUpstreamId, + CreateInvocationLogRequest, CreatePlatformApiKeyRequest, CreateSecretRequest, + CreateVersionRequest, CreateWorkspaceRequest, InvocationLogRecord, ListInvocationLogsQuery, + OperationAgentRef, OperationSampleMetadata, OperationSummary, OperationUsageSummary, + OperationVersionRecord, PlatformApiKeyRecord, PostgresRegistry, PublishAgentRequest, + PublishRequest, RegistryError, RegistryOperation, RotateSecretRequest, SampleKind, + SaveAgentBindingsRequest, SaveAuthProfileRequest, SaveSampleMetadataRequest, + SaveWorkspaceUpstreamRequest, UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, + UsageOperationBreakdown, UsageQuery, UsageSummary, UsageTimelinePoint, + WorkspaceMembershipRecord, WorkspaceRecord, WorkspaceUpstream, WorkspaceUpstreamId, }; use crank_runtime::{ PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation, @@ -154,120 +152,6 @@ pub struct TestRunResult { pub request_preview: Value, pub response_preview: Value, pub errors: Vec, - #[serde(skip_serializing_if = "Option::is_none")] - pub window: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub stream_session: Option, - #[serde(skip_serializing_if = "Option::is_none")] - pub async_job: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct WindowTestRunView { - pub window_complete: bool, - pub truncated: bool, - pub has_more: bool, - pub cursor: Option, -} - -#[derive(Clone, Debug, Serialize)] -pub struct StreamSessionStartView { - pub session_id: String, - pub status: StreamStatus, - #[serde(with = "time::serde::rfc3339")] - pub expires_at: OffsetDateTime, - pub poll_after_ms: u64, - pub preview: Value, -} - -#[derive(Clone, Debug, Serialize)] -pub struct AsyncJobStartView { - pub job_id: String, - pub status: JobStatus, - pub progress: Value, -} - -#[derive(Debug, Serialize, Deserialize)] -struct StoredSessionState { - input: Value, - summary: Value, - items: Vec, - next_index: usize, - batch_size: usize, -} - -enum TestRunOutcome { - Unary { - output: Value, - }, - Window { - output: crank_runtime::WindowExecutionResult, - }, - Session { - output: StreamSessionStartView, - }, - AsyncJob { - output: AsyncJobStartView, - }, -} - -impl TestRunOutcome { - fn into_result_views( - self, - ) -> ( - &'static str, - Value, - Option, - Option, - Option, - ) { - match self { - Self::Unary { output } => ("admin test run completed", output, None, None, None), - Self::Window { output } => ( - "admin window test run completed", - json!({ - "summary": output.summary, - "items": output.items, - "cursor": output.cursor, - "window_complete": output.window_complete, - "truncated": output.truncated, - "has_more": output.has_more, - }), - Some(WindowTestRunView { - window_complete: output.window_complete, - truncated: output.truncated, - has_more: output.has_more, - cursor: output.cursor, - }), - None, - None, - ), - Self::Session { output } => ( - "admin session test run started", - json!({ - "session_id": output.session_id, - "status": output.status, - "expires_at": format_timestamp(output.expires_at), - "poll_after_ms": output.poll_after_ms, - "preview": output.preview, - }), - None, - Some(output), - None, - ), - Self::AsyncJob { output } => ( - "admin async job test run started", - json!({ - "job_id": output.job_id, - "status": output.status, - "progress": output.progress, - }), - None, - None, - Some(output), - ), - } - } } #[derive(Clone, Debug, Deserialize)] @@ -437,12 +321,12 @@ pub struct UsageOverviewResponse { pub struct ProtocolCapabilityView { pub protocol: Protocol, pub supports_execution_modes: Vec, - pub supports_transport_behaviors: Vec, + pub supports_transport_behaviors: Vec, pub supports_auth_kinds: Vec, pub supports_upload_artifacts: Vec, pub supports_cursor_path: bool, pub supports_done_path: bool, - pub supports_aggregation_mode: Vec, + pub supports_aggregation_mode: Vec, } #[derive(Clone, Debug, Deserialize)] @@ -1801,12 +1685,7 @@ impl AdminService { .get_operation_version(workspace_id, operation_id, payload.version) .await?; let runtime = RuntimeOperation::from(record.snapshot.clone()); - let mode = runtime - .execution_config - .streaming - .as_ref() - .map(|streaming| streaming.mode) - .unwrap_or(ExecutionMode::Unary); + let mode = ExecutionMode::Unary; let request_preview = match build_request_preview(&record.snapshot.input_mapping, &payload.input) { Ok(preview) => preview, @@ -1835,9 +1714,6 @@ impl AdminService { errors: vec![crate::error::runtime_test_failure(&RuntimeError::Mapping( error, ))], - window: None, - stream_session: None, - async_job: None, }); } }; @@ -1847,9 +1723,8 @@ impl AdminService { .await; let started_at = std::time::Instant::now(); match match resolved_auth { - Ok(resolved_auth) => match mode { - ExecutionMode::Unary => self - .runtime + Ok(resolved_auth) => { + self.runtime .execute_with_auth_and_context( &runtime, &payload.input, @@ -1857,46 +1732,12 @@ impl AdminService { Some(&runtime_request_context), ) .await - .map(|output| TestRunOutcome::Unary { output }), - ExecutionMode::Window => self - .runtime - .execute_window_with_auth_and_context( - &runtime, - &payload.input, - resolved_auth.as_ref(), - Some(&runtime_request_context), - ) - .await - .map(|output| TestRunOutcome::Window { output }), - ExecutionMode::Session => self - .start_stream_session_test( - workspace_id, - &record.snapshot, - &runtime, - &payload.input, - resolved_auth.as_ref(), - &runtime_request_context, - ) - .await - .map(|output| TestRunOutcome::Session { output }), - ExecutionMode::AsyncJob => self - .start_async_job_test( - workspace_id, - &record.snapshot, - &runtime, - &payload.input, - &runtime_request_context, - ) - .await - .map(|output| TestRunOutcome::AsyncJob { output }), - }, + } Err(error) => Err(error), } { - Ok(outcome) => { + Ok(response_preview) => { let duration_ms = u64::try_from(started_at.elapsed().as_millis()).unwrap_or(u64::MAX); - let (message, response_preview, window, stream_session, async_job) = - outcome.into_result_views(); self.record_invocation(InvocationRecordRequest { workspace_id, agent_id: None, @@ -1905,7 +1746,7 @@ impl AdminService { source: InvocationSource::AdminTestRun, level: InvocationLevel::Info, status: InvocationStatus::Ok, - message: message.to_owned(), + message: "admin test run completed".to_owned(), status_code: None, error_kind: None, duration_ms, @@ -1919,9 +1760,6 @@ impl AdminService { request_preview, response_preview, errors: Vec::new(), - window, - stream_session, - async_job, }) } Err(error) => { @@ -1949,9 +1787,6 @@ impl AdminService { request_preview, response_preview: Value::Null, errors: vec![crate::error::runtime_test_failure(&error)], - window: None, - stream_session: None, - async_job: None, }) } } @@ -1983,6 +1818,7 @@ impl AdminService { .map(Some) } + #[cfg(any())] async fn start_stream_session_test( &self, workspace_id: &WorkspaceId, @@ -2060,6 +1896,7 @@ impl AdminService { }) } + #[cfg(any())] async fn start_async_job_test( &self, workspace_id: &WorkspaceId, @@ -3230,7 +3067,6 @@ impl AdminService { fn validate_operation_payload(&self, payload: &OperationPayload) -> Result<(), ApiError> { self.validate_operation_capabilities(payload.protocol, payload.security_level)?; validate_protocol_target(payload.protocol, &payload.target)?; - validate_streaming_policy(&payload.execution_config)?; validate_response_cache_policy(&payload.target, &payload.execution_config)?; payload.input_mapping.validate_paths()?; payload.output_mapping.validate_paths()?; @@ -3240,7 +3076,6 @@ impl AdminService { fn validate_registry_operation(&self, operation: &RegistryOperation) -> Result<(), ApiError> { self.validate_operation_capabilities(operation.protocol, operation.security_level)?; validate_protocol_target(operation.protocol, &operation.target)?; - validate_streaming_policy(&operation.execution_config)?; validate_response_cache_policy(&operation.target, &operation.execution_config)?; operation.input_mapping.validate_paths()?; operation.output_mapping.validate_paths()?; @@ -3640,21 +3475,12 @@ fn build_request_preview( "path": prepared.path_params, "query": prepared.query_params, "headers": prepared.headers, - "grpc": prepared.grpc.unwrap_or(Value::Null), - "variables": prepared.variables.unwrap_or(Value::Null), "body": prepared.body.unwrap_or(Value::Null) })) } fn validate_protocol_target(protocol: Protocol, target: &Target) -> Result<(), ApiError> { - let is_match = matches!( - (protocol, target), - (Protocol::Rest, Target::Rest(_)) - | (Protocol::Graphql, Target::Graphql(_)) - | (Protocol::Grpc, Target::Grpc(_)) - | (Protocol::Websocket, Target::Websocket(_)) - | (Protocol::Soap, Target::Soap(_)) - ); + let is_match = matches!((protocol, target), (Protocol::Rest, Target::Rest(_))); if is_match { return Ok(()); @@ -3825,8 +3651,6 @@ fn demo_rest_operation_payload() -> OperationPayload { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: crank_core::ToolDescription { title: "Create CRM Lead".to_owned(), @@ -3893,8 +3717,6 @@ fn demo_archived_operation_payload() -> OperationPayload { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: crank_core::ToolDescription { title: "Archive Marketing Contact".to_owned(), @@ -3977,85 +3799,16 @@ fn map_identity_error(error: IdentityError) -> ApiError { } } -async fn resolve_runtime_auth_for_task( - registry: &PostgresRegistry, - secret_crypto: &SecretCrypto, - workspace_id: &WorkspaceId, - execution_config: &crank_core::ExecutionConfig, -) -> Result, RuntimeError> { - let Some(auth_profile_id) = execution_config.auth_profile_ref.as_ref() else { - return Ok(None); - }; - - let auth_profile = registry - .get_auth_profile(workspace_id, auth_profile_id) - .await - .map_err(|error| RuntimeError::SecretCrypto { - operation: "load auth profile", - details: error.to_string(), - })? - .ok_or_else(|| RuntimeError::MissingAuthProfile { - auth_profile_id: auth_profile_id.as_str().to_owned(), - })?; - - let mut secrets = BTreeMap::new(); - let used_at = OffsetDateTime::now_utc(); - - for secret_id in auth_profile.config.secret_ids() { - let secret = registry - .get_secret(workspace_id, secret_id) - .await - .map_err(|error| RuntimeError::SecretCrypto { - operation: "load secret", - details: error.to_string(), - })? - .ok_or_else(|| RuntimeError::MissingSecret { - secret_id: secret_id.as_str().to_owned(), - })?; - let version = registry - .get_current_secret_version(workspace_id, secret_id) - .await - .map_err(|error| RuntimeError::SecretCrypto { - operation: "load current secret version", - details: error.to_string(), - })? - .ok_or_else(|| RuntimeError::MissingSecretVersion { - secret_id: secret_id.as_str().to_owned(), - version: secret.secret.current_version, - })?; - let plaintext = secret_crypto.decrypt( - &version.secret_version.key_version, - &version.secret_version.ciphertext, - )?; - registry - .touch_secret(workspace_id, secret_id, &used_at) - .await - .map_err(|error| RuntimeError::SecretCrypto { - operation: "touch secret", - details: error.to_string(), - })?; - secrets.insert(secret_id.clone(), plaintext); - } - - ResolvedAuth::from_profile(&auth_profile, &secrets).map(Some) -} - fn runtime_error_code(error: &RuntimeError) -> &'static str { match error { RuntimeError::Schema(_) => "schema_error", RuntimeError::Mapping(_) => "mapping_error", RuntimeError::InvalidPreparedRequest { .. } => "invalid_request", - RuntimeError::GraphqlAdapter(_) => "graphql_error", - RuntimeError::GrpcAdapter(_) => "grpc_error", RuntimeError::RestAdapter(_) => "rest_error", RuntimeError::ProtocolAdapter(_) => "adapter_error", - RuntimeError::SoapAdapter(_) => "soap_error", - RuntimeError::WebsocketAdapter(_) => "websocket_error", RuntimeError::UnsupportedProtocol { .. } => "unsupported_protocol", RuntimeError::ConcurrencyLimitExceeded { .. } => "runtime_overloaded", - RuntimeError::MissingStreamingConfig { .. } => "streaming_config_error", RuntimeError::UnsupportedExecutionMode { .. } => "streaming_mode_error", - RuntimeError::InvalidStreamingPayload { .. } => "streaming_payload_error", RuntimeError::MissingAuthProfile { .. } => "auth_profile_not_found", RuntimeError::MissingSecret { .. } | RuntimeError::MissingSecretVersion { .. } => { "secret_not_found" @@ -4065,40 +3818,16 @@ fn runtime_error_code(error: &RuntimeError) -> &'static str { } } -fn protocol_capability_view(protocol: Protocol, edition: ProductEdition) -> ProtocolCapabilityView { - let community_build = matches!(edition, ProductEdition::Community); - let supports_execution_modes = if community_build { - vec![ExecutionMode::Unary] - } else { - [ - ExecutionMode::Unary, - ExecutionMode::Window, - ExecutionMode::Session, - ExecutionMode::AsyncJob, - ] - .into_iter() - .filter(|mode| protocol.supports_execution_mode(*mode)) - .collect() - }; - let supports_transport_behaviors = if community_build { - vec![TransportBehavior::RequestResponse] - } else { - [ - TransportBehavior::RequestResponse, - TransportBehavior::ServerStream, - TransportBehavior::StatefulSession, - TransportBehavior::DeferredResult, - ] - .into_iter() - .filter(|behavior| protocol.supports_transport_behavior(*behavior)) - .collect() - }; +fn protocol_capability_view( + protocol: Protocol, + _edition: ProductEdition, +) -> ProtocolCapabilityView { let supports_upload_artifacts = Vec::new(); ProtocolCapabilityView { protocol, - supports_execution_modes, - supports_transport_behaviors, + supports_execution_modes: vec![ExecutionMode::Unary], + supports_transport_behaviors: vec!["request_response".to_owned()], supports_auth_kinds: vec![ "none".to_owned(), "bearer".to_owned(), @@ -4107,15 +3836,9 @@ fn protocol_capability_view(protocol: Protocol, edition: ProductEdition) -> Prot "api_key_query".to_owned(), ], supports_upload_artifacts, - supports_cursor_path: !matches!(protocol, Protocol::Graphql), - supports_done_path: !matches!(protocol, Protocol::Graphql), - supports_aggregation_mode: vec![ - AggregationMode::RawItems, - AggregationMode::SummaryOnly, - AggregationMode::SummaryPlusSamples, - AggregationMode::Stats, - AggregationMode::LatestState, - ], + supports_cursor_path: false, + supports_done_path: false, + supports_aggregation_mode: Vec::new(), } } @@ -4246,22 +3969,6 @@ fn agent_mcp_endpoint(workspace_slug: &str, agent_slug: &str) -> String { format!("/mcp/v1/{workspace_slug}/{agent_slug}") } -fn validate_streaming_policy( - execution_config: &crank_core::ExecutionConfig, -) -> Result<(), ApiError> { - if execution_config.streaming.is_some() { - return Err(ApiError::validation_with_context( - "streaming execution is not supported in Community".to_owned(), - json!({ - "field": "execution_config.streaming", - "edition": "community", - }), - )); - } - - Ok(()) -} - fn validate_response_cache_policy( target: &Target, execution_config: &crank_core::ExecutionConfig, @@ -4279,15 +3986,6 @@ fn validate_response_cache_policy( )); } - if execution_config.streaming.is_some() { - return Err(ApiError::validation_with_context( - "response cache is supported only for unary operations".to_owned(), - json!({ - "field": "execution_config.response_cache", - }), - )); - } - match target { Target::Rest(rest_target) if rest_target.method == crank_core::HttpMethod::Get => {} _ => { @@ -4330,8 +4028,6 @@ mod tests { response_cache: Some(ResponseCachePolicy { ttl_ms: 5_000 }), auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, } } @@ -4350,14 +4046,12 @@ mod tests { } #[test] - fn rejects_response_cache_for_non_rest_target() { - let target = Target::Graphql(crank_core::GraphqlTarget { - endpoint: "http://example.invalid/graphql".to_owned(), - operation_type: crank_core::GraphqlOperationType::Query, - operation_name: "LookupLead".to_owned(), - query_template: - "query LookupLead($email: String!) { lookupLead(email: $email) { id } }".to_owned(), - response_path: "$.response.body.data.lookupLead".to_owned(), + fn rejects_response_cache_for_non_get_rest_operation() { + let target = Target::Rest(RestTarget { + base_url: "http://example.invalid".to_owned(), + method: HttpMethod::Post, + path_template: "/catalog".to_owned(), + static_headers: BTreeMap::new(), }); let error = diff --git a/apps/mcp-server/src/main.rs b/apps/mcp-server/src/main.rs index a712440..bef3111 100644 --- a/apps/mcp-server/src/main.rs +++ b/apps/mcp-server/src/main.rs @@ -69,9 +69,6 @@ async fn main() -> Result<(), Box> { info!( runtime_max_concurrent_unary = runtime_limits.max_concurrent_unary, - runtime_max_concurrent_window = runtime_limits.max_concurrent_window, - runtime_max_concurrent_sessions = runtime_limits.max_concurrent_sessions, - runtime_max_concurrent_jobs = runtime_limits.max_concurrent_jobs, mcp_rate_limit_rps = api_rate_limit.requests_per_second, mcp_rate_limit_burst = api_rate_limit.burst, cache_backend = %cache_config.backend, @@ -3124,8 +3121,6 @@ mod tests { response_cache: None, auth_profile_ref: None, headers: BTreeMap::new(), - protocol_options: None, - streaming: None, }, tool_description: ToolDescription { title: "Create Lead".to_owned(), diff --git a/apps/ui/html/agents.html b/apps/ui/html/agents.html index 58dd05f..fe89031 100644 --- a/apps/ui/html/agents.html +++ b/apps/ui/html/agents.html @@ -41,9 +41,6 @@ -
Select which operations this agent exposes. LLMs connecting to this agent will only see these tools.
+
Select the MCP tools available to this agent.
diff --git a/apps/ui/html/api-keys.html b/apps/ui/html/api-keys.html index 51dff63..fefbe21 100644 --- a/apps/ui/html/api-keys.html +++ b/apps/ui/html/api-keys.html @@ -59,11 +59,6 @@ diff --git a/apps/ui/html/wizard/step2.html b/apps/ui/html/wizard/step2.html index cb7c1c5..0337547 100644 --- a/apps/ui/html/wizard/step2.html +++ b/apps/ui/html/wizard/step2.html @@ -1,12 +1,12 @@ - +
Step 2 of 5
-

Select upstream & endpoint

-

Choose an existing upstream — a shared host with its auth config — or register a new one. Then define the specific endpoint path this operation will call.

+

Выберите API-хост и путь

+

Выберите существующий API-хост или добавьте новый. Затем задайте путь, который будет вызывать эта операция.

@@ -21,10 +21,10 @@
-
Upstream
-
Shared host, base URL, and authentication
+
API-хост
+
Базовый URL и способ авторизации
- Required + Обязательно
@@ -32,7 +32,7 @@
- Select an upstream… + Выберите API-хост…
@@ -45,7 +45,7 @@ -
@@ -58,7 +58,7 @@
- +
@@ -69,7 +69,7 @@ - Register new upstream + Добавить новый API-хост
@@ -77,86 +77,86 @@
- + -
Unique identifier for this upstream.
+
Уникальное имя этого API-хоста.
- + -
Root URL without a trailing slash. Auth is configured separately below.
+
Корневой URL без завершающего слеша. Авторизация настраивается ниже.
- + -
Use secrets-backed auth profiles instead of embedding credential headers in the upstream definition.
+
Используйте секреты, чтобы не хранить токены и пароли прямо в настройках API-хоста.
- +
@@ -165,11 +165,11 @@
-
Optional non-secret headers sent on every request to this upstream. Use auth profiles for credentials.
+
Необязательные заголовки без секретных значений. Токены, пароли и ключи храните через профиль авторизации.
- - + +
@@ -186,19 +186,19 @@
-
Endpoint
-
Path and HTTP method for this specific operation
+
Путь API
+
Путь и HTTP-метод для конкретной операции
- Required + Обязательно
-
Appended to the upstream base URL. Use {param} for path variables.
+
Добавляется к базовому URL API-хоста. Используйте {param} для параметров пути.
diff --git a/apps/ui/html/wizard/step3-rest.html b/apps/ui/html/wizard/step3-rest.html index 0273706..faf4a07 100644 --- a/apps/ui/html/wizard/step3-rest.html +++ b/apps/ui/html/wizard/step3-rest.html @@ -5,8 +5,8 @@
Step 3 of 5 -

HTTP method & format

-

Choose the HTTP verb this operation sends and configure content negotiation headers. Crank will serialize MCP tool arguments into the appropriate request body format.

+

HTTP метод и формат

+

Выберите HTTP-метод и формат запроса. Crank подставит параметры MCP инструмента в запрос к API.

@@ -18,8 +18,8 @@
-
HTTP method
-
Verb sent to the upstream on every tool invocation
+
HTTP метод
+
Метод, который отправляется в API при каждом вызове инструмента
Required @@ -27,23 +27,23 @@
@@ -60,8 +60,8 @@
-
Request format
-
Content negotiation and serialisation
+
Формат запроса
+
Формат данных запроса и ожидаемого ответа
Optional @@ -75,7 +75,7 @@ -
How the request body is encoded when sent to the upstream.
+
В каком формате отправлять данные в API.
@@ -84,7 +84,7 @@ -
Accepted response content types from the upstream server.
+
Какой формат ответа ожидать от API.
diff --git a/apps/ui/html/wizard/step4.html b/apps/ui/html/wizard/step4.html index 2fb425c..9238cb1 100644 --- a/apps/ui/html/wizard/step4.html +++ b/apps/ui/html/wizard/step4.html @@ -4,8 +4,8 @@
Step 4 of 5 -

Tool config

-

Name your tool, write the LLM-facing description, and define the input/output schemas. The description is the primary signal the model uses when deciding whether to call this tool.

+

Настройки инструмента

+

Задайте имя инструмента, описание для LLM и определите входную/выходную схему. Описание — главный сигнал, по которому модель решает, вызывать ли этот инструмент.

@@ -16,47 +16,47 @@
-
Tool identity
-
Machine-readable name and LLM-facing description
+
Параметры инструмента
+
Имя инструмента и описание его назначения
-
Machine-readable identifier used in MCP tool calls. Only lowercase letters, numbers and underscores. Cannot be changed after publishing.
+
Техническое имя для вызова MCP инструмента. Допустимы только строчные английские буквы, цифры и подчеркивания. После публикации изменить нельзя.
-
Human-readable name shown in the console and audit log.
+
Понятное название, которое отображается в интерфейсе и логах.
-
Short imperative sentence shown in the MCP tool manifest.
+
Короткая фраза, которая будет показана MCP клиенту в описании инструмента.
-
LLM-facing description. Be precise — this is the primary signal the model uses to decide whether to invoke this tool.
+
Описание для модели. Пишите точно: по этому тексту модель решает, когда нужно вызвать инструмент.
@@ -68,8 +68,8 @@
-
Writing effective descriptions
-
Start with a verb ("Creates", "Fetches", "Updates"). Mention key input requirements. Describe what a successful response looks like. Avoid vague phrasing like "handles" or "manages". See description best practices →
+
Как писать хорошие описания
+
Начинайте с глагола: создает, получает, обновляет. Укажите обязательные входные данные и опишите успешный результат. Избегайте расплывчатых формулировок вроде “обрабатывает” или “управляет”.
@@ -82,8 +82,8 @@
-
Input schema
-
Parameters the LLM passes when calling this tool
+
Входная схема
+
Параметры, которые LLM передает при вызове инструмента
JSON Schema draft-07 @@ -120,8 +120,8 @@
-
Output schema
-
Shape of the data returned to the LLM after a successful call
+
Выходная схема
+
Форма данных, которые LLM получает после успешного вызова
JSON Schema draft-07 @@ -151,8 +151,8 @@
-
Schemas are protocol-agnostic
-
These schemas describe the MCP contract, not the upstream wire format. Field mapping in Step 5 translates between the two. You can upload a sample JSON response to auto-generate the output schema.
+
Схемы описывают инструмент
+
Входная схема описывает данные, которые MCP клиент передает инструменту. Выходная схема описывает результат работы инструмента. На следующем шаге эти поля связываются с реальным API-запросом и ответом.
diff --git a/apps/ui/html/wizard/step5.html b/apps/ui/html/wizard/step5.html index 445858e..05c4d4b 100644 --- a/apps/ui/html/wizard/step5.html +++ b/apps/ui/html/wizard/step5.html @@ -4,12 +4,12 @@
Step 5 of 5 -

Mapping and execution

-

Define how MCP tool arguments map to upstream request fields, and how the upstream response maps back to MCP output. Then set execution parameters — auth is configured per-upstream in step 2.

+

Связи полей и запуск

+

Свяжите параметры MCP инструмента с полями API-запроса, а ответ API с результатом инструмента. Авторизация настраивается на шаге 2.

- Input → Request + Вход инструмента → API-запрос
@@ -18,7 +18,7 @@
- yaml / input-mapping + yaml / request-fields
- Response → Output + Ответ API → результат инструмента
@@ -40,7 +40,7 @@ source: "$.input.source"
- yaml / output-mapping + yaml / response-fields
- Execution + Параметры выполнения
@@ -64,8 +64,8 @@ owner_id: "$.response.data.owner.id"
-
Execution config
-
Timeouts, retry policy and auth profile reference
+
Выполнение запроса
+
Время ожидания, повторные попытки и профиль авторизации
Optional @@ -73,7 +73,7 @@ owner_id: "$.response.data.owner.id"
- yaml / exec-config + yaml / execution
- +
- - + +
- +
- +
- +
@@ -214,20 +192,20 @@ tls:
-
YAML import and export
-
Move operation drafts between environments without leaving the wizard.
+
Импорт и экспорт YAML
+
Экспортируйте операцию в файл или импортируйте подготовленную YAML-конфигурацию.
- - - + + +
- - + +
@@ -240,26 +218,15 @@ tls:
-
Publish
-
Save the current draft and expose it to published agents.
+
Публикация
+
Опубликуйте операцию, чтобы ее можно было добавить агентам.
-
Publishing uses the current draft version from this wizard session.
+
Перед публикацией Crank сохраняет текущие настройки из мастера.
- +
- -
- - - - -
-
Operation stays editable in this wizard
-
Saving creates or updates the current draft in place. Use the live actions above to upload samples, test the draft, export or import YAML, and publish when the upstream contract is ready.
-
-
diff --git a/apps/ui/html/workspace-setup.html b/apps/ui/html/workspace-setup.html index eb008a2..de8e75e 100644 --- a/apps/ui/html/workspace-setup.html +++ b/apps/ui/html/workspace-setup.html @@ -75,7 +75,7 @@
- +
diff --git a/apps/ui/index.html b/apps/ui/index.html index 8f8a9fa..5e0d71a 100644 --- a/apps/ui/index.html +++ b/apps/ui/index.html @@ -44,10 +44,6 @@ - - @@ -90,7 +86,7 @@ - -
- - -
-