pyfect-dev 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pyfect/__init__.py +729 -0
- pyfect/_all.py +242 -0
- pyfect/_background.py +28 -0
- pyfect/_binary_codecs.py +124 -0
- pyfect/_cached.py +165 -0
- pyfect/_catch.py +128 -0
- pyfect/_catch_reason.py +574 -0
- pyfect/_concurrency.py +291 -0
- pyfect/_context_effects.py +163 -0
- pyfect/_context_managers.py +76 -0
- pyfect/_core.py +368 -0
- pyfect/_digits.py +71 -0
- pyfect/_effect_args.py +46 -0
- pyfect/_effect_base.py +71 -0
- pyfect/_effect_concurrency.py +133 -0
- pyfect/_effect_eager.py +103 -0
- pyfect/_effect_errors.py +344 -0
- pyfect/_effect_folding.py +110 -0
- pyfect/_effect_hash.py +140 -0
- pyfect/_effect_logging.py +57 -0
- pyfect/_effect_metrics.py +102 -0
- pyfect/_effect_resources.py +179 -0
- pyfect/_effect_services.py +25 -0
- pyfect/_effect_timing.py +199 -0
- pyfect/_effect_tracing.py +99 -0
- pyfect/_equal.py +195 -0
- pyfect/_extras.py +43 -0
- pyfect/_fiber.py +969 -0
- pyfect/_fiber_all.py +134 -0
- pyfect/_fiber_collections.py +106 -0
- pyfect/_fn.py +706 -0
- pyfect/_hamt.py +174 -0
- pyfect/_interrupt_reason.py +33 -0
- pyfect/_isaac.py +149 -0
- pyfect/_iterables.py +231 -0
- pyfect/_js.py +263 -0
- pyfect/_js_date.py +87 -0
- pyfect/_json_text.py +286 -0
- pyfect/_json_value.py +9 -0
- pyfect/_layer_core.py +403 -0
- pyfect/_layer_for_life.py +33 -0
- pyfect/_layer_tracing.py +70 -0
- pyfect/_locked_ref.py +147 -0
- pyfect/_loop_thread.py +152 -0
- pyfect/_metric_ref.py +29 -0
- pyfect/_one_or_many.py +14 -0
- pyfect/_os_wait.py +118 -0
- pyfect/_path_posix.py +129 -0
- pyfect/_primitives.py +434 -0
- pyfect/_public.py +36 -0
- pyfect/_reasons.py +64 -0
- pyfect/_released.py +67 -0
- pyfect/_root.py +87 -0
- pyfect/_round_robin.py +73 -0
- pyfect/_some.py +17 -0
- pyfect/_stack.py +52 -0
- pyfect/_stack_frame.py +114 -0
- pyfect/_string_hash.py +35 -0
- pyfect/_subscriber_queue.py +47 -0
- pyfect/_tags.py +14 -0
- pyfect/_tracer_ref.py +21 -0
- pyfect/_tx_ops.py +77 -0
- pyfect/_unsubscripted.py +23 -0
- pyfect/_url.py +54 -0
- pyfect/_waiters.py +127 -0
- pyfect/_wake_tracking.py +98 -0
- pyfect/_wire_codec.py +123 -0
- pyfect/ai/__init__.py +73 -0
- pyfect/ai/_approvals.py +249 -0
- pyfect/ai/_decision_answers.py +185 -0
- pyfect/ai/_generated_client.py +298 -0
- pyfect/ai/_generated_support.py +84 -0
- pyfect/ai/_mcp/__init__.py +1 -0
- pyfect/ai/_mcp/batch.py +68 -0
- pyfect/ai/_mcp/client_transport.py +412 -0
- pyfect/ai/_mcp/client_wire.py +284 -0
- pyfect/ai/_mcp/core.py +490 -0
- pyfect/ai/_mcp/dispatch.py +1048 -0
- pyfect/ai/_mcp/http.py +462 -0
- pyfect/ai/_mcp/http_admission.py +269 -0
- pyfect/ai/_mcp/http_status.py +27 -0
- pyfect/ai/_mcp/input_wire.py +226 -0
- pyfect/ai/_mcp/log_levels.py +55 -0
- pyfect/ai/_mcp/param_headers.py +63 -0
- pyfect/ai/_mcp/projection.py +248 -0
- pyfect/ai/_mcp/register.py +683 -0
- pyfect/ai/_mcp/reverse_policy.py +155 -0
- pyfect/ai/_mcp/routing_header.py +24 -0
- pyfect/ai/_mcp/sessions.py +114 -0
- pyfect/ai/_mcp/stdio.py +110 -0
- pyfect/ai/_mcp/subscriptions.py +134 -0
- pyfect/ai/_mcp/tool_schema.py +35 -0
- pyfect/ai/_noul_answers.py +64 -0
- pyfect/ai/_openai_all_of.py +137 -0
- pyfect/ai/_openai_errors.py +233 -0
- pyfect/ai/_provider.py +439 -0
- pyfect/ai/_provider_tools.py +129 -0
- pyfect/ai/_stream_resolution.py +231 -0
- pyfect/ai/_structured_output_checks.py +138 -0
- pyfect/ai/_structured_output_codec.py +229 -0
- pyfect/ai/_structured_output_json.py +109 -0
- pyfect/ai/_tool_call_ids.py +80 -0
- pyfect/ai/ai_error.py +785 -0
- pyfect/ai/anthropic/__init__.py +28 -0
- pyfect/ai/anthropic/_errors.py +164 -0
- pyfect/ai/anthropic/_prompt.py +375 -0
- pyfect/ai/anthropic/_response.py +443 -0
- pyfect/ai/anthropic/_schema.py +206 -0
- pyfect/ai/anthropic/_stream.py +377 -0
- pyfect/ai/anthropic/_tools.py +248 -0
- pyfect/ai/anthropic/client.py +289 -0
- pyfect/ai/anthropic/config.py +32 -0
- pyfect/ai/anthropic/error.py +27 -0
- pyfect/ai/anthropic/language_model.py +366 -0
- pyfect/ai/anthropic/telemetry.py +51 -0
- pyfect/ai/anthropic/tool.py +570 -0
- pyfect/ai/anthropic_structured_output.py +138 -0
- pyfect/ai/chat.py +548 -0
- pyfect/ai/cloudflare/__init__.py +15 -0
- pyfect/ai/cloudflare/_errors.py +108 -0
- pyfect/ai/cloudflare/client.py +174 -0
- pyfect/ai/cloudflare/decision_model.py +117 -0
- pyfect/ai/cloudflare/schema.py +144 -0
- pyfect/ai/decision.py +290 -0
- pyfect/ai/decision_model.py +283 -0
- pyfect/ai/embedding_model.py +177 -0
- pyfect/ai/id_generator.py +79 -0
- pyfect/ai/language_model.py +1077 -0
- pyfect/ai/mcp_client.py +682 -0
- pyfect/ai/mcp_client_error.py +223 -0
- pyfect/ai/mcp_protocol.py +205 -0
- pyfect/ai/mcp_schema.py +1300 -0
- pyfect/ai/mcp_server.py +404 -0
- pyfect/ai/model.py +56 -0
- pyfect/ai/openai/__init__.py +49 -0
- pyfect/ai/openai/_errors.py +89 -0
- pyfect/ai/openai/_prompt.py +527 -0
- pyfect/ai/openai/_requests.py +34 -0
- pyfect/ai/openai/_response.py +569 -0
- pyfect/ai/openai/_schema.py +769 -0
- pyfect/ai/openai/_stream.py +771 -0
- pyfect/ai/openai/_tools.py +128 -0
- pyfect/ai/openai/_web_socket.py +295 -0
- pyfect/ai/openai/client.py +296 -0
- pyfect/ai/openai/client_generated.py +123 -0
- pyfect/ai/openai/config.py +34 -0
- pyfect/ai/openai/decision_model.py +177 -0
- pyfect/ai/openai/embedding_model.py +116 -0
- pyfect/ai/openai/error.py +8 -0
- pyfect/ai/openai/generated/__init__.py +7213 -0
- pyfect/ai/openai/generated/_recursive.py +18 -0
- pyfect/ai/openai/generated/_schemas_0.py +1015 -0
- pyfect/ai/openai/generated/_schemas_1.py +1016 -0
- pyfect/ai/openai/generated/_schemas_2.py +1017 -0
- pyfect/ai/openai/generated/_schemas_3.py +615 -0
- pyfect/ai/openai/generated/client.py +5298 -0
- pyfect/ai/openai/generated/types.py +8123 -0
- pyfect/ai/openai/language_model.py +304 -0
- pyfect/ai/openai/telemetry.py +19 -0
- pyfect/ai/openai/tool.py +391 -0
- pyfect/ai/openai_compat/__init__.py +25 -0
- pyfect/ai/openai_compat/_prompt.py +191 -0
- pyfect/ai/openai_compat/_request.py +153 -0
- pyfect/ai/openai_compat/_response.py +407 -0
- pyfect/ai/openai_compat/_schema.py +97 -0
- pyfect/ai/openai_compat/client.py +234 -0
- pyfect/ai/openai_compat/config.py +32 -0
- pyfect/ai/openai_compat/embedding_model.py +139 -0
- pyfect/ai/openai_compat/error.py +7 -0
- pyfect/ai/openai_compat/language_model.py +245 -0
- pyfect/ai/openai_compat/telemetry.py +51 -0
- pyfect/ai/openai_structured_output.py +118 -0
- pyfect/ai/openrouter/__init__.py +36 -0
- pyfect/ai/openrouter/_errors.py +186 -0
- pyfect/ai/openrouter/_prompt.py +272 -0
- pyfect/ai/openrouter/_response.py +226 -0
- pyfect/ai/openrouter/_stream.py +320 -0
- pyfect/ai/openrouter/client.py +283 -0
- pyfect/ai/openrouter/config.py +36 -0
- pyfect/ai/openrouter/decision_model.py +161 -0
- pyfect/ai/openrouter/error.py +23 -0
- pyfect/ai/openrouter/generated/__init__.py +3029 -0
- pyfect/ai/openrouter/generated/_recursive.py +15 -0
- pyfect/ai/openrouter/generated/_schemas_0.py +1015 -0
- pyfect/ai/openrouter/generated/_schemas_1.py +522 -0
- pyfect/ai/openrouter/generated/client.py +1445 -0
- pyfect/ai/openrouter/generated/types.py +4769 -0
- pyfect/ai/openrouter/language_model.py +345 -0
- pyfect/ai/openrouter/schema.py +165 -0
- pyfect/ai/prompt.py +664 -0
- pyfect/ai/response.py +768 -0
- pyfect/ai/response_id_tracker.py +82 -0
- pyfect/ai/telemetry.py +126 -0
- pyfect/ai/tokenizer.py +61 -0
- pyfect/ai/tool.py +536 -0
- pyfect/ai/toolkit.py +490 -0
- pyfect/ai/typesafe/__init__.py +17 -0
- pyfect/ai/typesafe/_errors.py +120 -0
- pyfect/ai/typesafe/client.py +146 -0
- pyfect/ai/typesafe/config.py +34 -0
- pyfect/ai/typesafe/decision_model.py +90 -0
- pyfect/ai/typesafe/schema.py +140 -0
- pyfect/batching/__init__.py +58 -0
- pyfect/batching/_api.py +71 -0
- pyfect/batching/_engine.py +229 -0
- pyfect/batching/_from_effect.py +76 -0
- pyfect/batching/_persisted.py +98 -0
- pyfect/batching/_request.py +133 -0
- pyfect/batching/_resolver.py +477 -0
- pyfect/batching/_with_cache.py +90 -0
- pyfect/big_decimal.py +500 -0
- pyfect/brand.py +114 -0
- pyfect/byte_size.py +354 -0
- pyfect/cache/__init__.py +29 -0
- pyfect/cache/_cache.py +426 -0
- pyfect/cache/_expiry.py +69 -0
- pyfect/cache/_scoped_cache.py +546 -0
- pyfect/case.py +174 -0
- pyfect/cause.py +462 -0
- pyfect/channel/__init__.py +155 -0
- pyfect/channel/_base.py +92 -0
- pyfect/channel/_catch.py +158 -0
- pyfect/channel/_catch_reason.py +572 -0
- pyfect/channel/_channel.py +158 -0
- pyfect/channel/_combinators.py +220 -0
- pyfect/channel/_concurrency_methods.py +217 -0
- pyfect/channel/_concurrent_map.py +118 -0
- pyfect/channel/_constructors.py +349 -0
- pyfect/channel/_context.py +232 -0
- pyfect/channel/_error_methods.py +336 -0
- pyfect/channel/_errors.py +180 -0
- pyfect/channel/_filters.py +225 -0
- pyfect/channel/_lifecycle.py +71 -0
- pyfect/channel/_lifecycle_methods.py +206 -0
- pyfect/channel/_loop.py +100 -0
- pyfect/channel/_merge.py +246 -0
- pyfect/channel/_repeat.py +154 -0
- pyfect/channel/_run.py +149 -0
- pyfect/channel/_run_methods.py +341 -0
- pyfect/channel/_sinks.py +88 -0
- pyfect/channel/_sources.py +238 -0
- pyfect/channel/_text.py +176 -0
- pyfect/channel/_transform_methods.py +415 -0
- pyfect/channel/_upstream.py +67 -0
- pyfect/channel_schema.py +131 -0
- pyfect/cli/__init__.py +79 -0
- pyfect/cli/_ansi.py +82 -0
- pyfect/cli/_command.py +469 -0
- pyfect/cli/_config.py +164 -0
- pyfect/cli/_environment.py +10 -0
- pyfect/cli/_help.py +195 -0
- pyfect/cli/_lexer.py +81 -0
- pyfect/cli/_parser.py +465 -0
- pyfect/cli/_settings.py +85 -0
- pyfect/cli/_suggest.py +33 -0
- pyfect/cli/_width.py +134 -0
- pyfect/cli/_wizard.py +317 -0
- pyfect/cli/argument.py +212 -0
- pyfect/cli/cli_config.py +40 -0
- pyfect/cli/cli_error.py +242 -0
- pyfect/cli/cli_output.py +305 -0
- pyfect/cli/command.py +440 -0
- pyfect/cli/completions/__init__.py +50 -0
- pyfect/cli/completions/_completer.ps1 +190 -0
- pyfect/cli/completions/_descriptor.py +182 -0
- pyfect/cli/completions/bash.py +300 -0
- pyfect/cli/completions/fish.py +164 -0
- pyfect/cli/completions/powershell.py +156 -0
- pyfect/cli/completions/zsh.py +189 -0
- pyfect/cli/flag.py +246 -0
- pyfect/cli/global_flag.py +99 -0
- pyfect/cli/help_doc.py +63 -0
- pyfect/cli/param.py +789 -0
- pyfect/cli/primitive.py +308 -0
- pyfect/cli/prompt/__init__.py +75 -0
- pyfect/cli/prompt/_auto_complete.py +205 -0
- pyfect/cli/prompt/_choices.py +104 -0
- pyfect/cli/prompt/_confirm.py +195 -0
- pyfect/cli/prompt/_core.py +512 -0
- pyfect/cli/prompt/_date.py +173 -0
- pyfect/cli/prompt/_date_parts.py +387 -0
- pyfect/cli/prompt/_file.py +372 -0
- pyfect/cli/prompt/_multi_select.py +204 -0
- pyfect/cli/prompt/_number.py +257 -0
- pyfect/cli/prompt/_select.py +117 -0
- pyfect/cli/prompt/_text.py +239 -0
- pyfect/cli/terminal_capabilities.py +118 -0
- pyfect/clock.py +150 -0
- pyfect/cluster/__init__.py +155 -0
- pyfect/cluster/_cluster_abandon.py +36 -0
- pyfect/cluster/_cluster_layer.py +122 -0
- pyfect/cluster/_entity_activation.py +16 -0
- pyfect/cluster/_entity_manager/__init__.py +22 -0
- pyfect/cluster/_entity_manager/_activation.py +540 -0
- pyfect/cluster/_entity_manager/_core.py +186 -0
- pyfect/cluster/_entity_manager/_decode.py +60 -0
- pyfect/cluster/_entity_manager/_manager.py +423 -0
- pyfect/cluster/_entity_manager/_state.py +113 -0
- pyfect/cluster/_entity_reaper.py +94 -0
- pyfect/cluster/_hash.py +13 -0
- pyfect/cluster/_interruptors.py +88 -0
- pyfect/cluster/_resource_map.py +179 -0
- pyfect/cluster/_resource_ref.py +266 -0
- pyfect/cluster/_shard_lock.py +14 -0
- pyfect/cluster/cluster_cron.py +230 -0
- pyfect/cluster/cluster_error.py +81 -0
- pyfect/cluster/cluster_http.py +210 -0
- pyfect/cluster/cluster_metrics.py +54 -0
- pyfect/cluster/cluster_schema.py +137 -0
- pyfect/cluster/cluster_socket.py +197 -0
- pyfect/cluster/cluster_workflow_engine/__init__.py +24 -0
- pyfect/cluster/cluster_workflow_engine/_engine.py +1099 -0
- pyfect/cluster/cluster_workflow_engine/_entities.py +298 -0
- pyfect/cluster/deliver_at.py +31 -0
- pyfect/cluster/entity/__init__.py +39 -0
- pyfect/cluster/entity/_entity.py +332 -0
- pyfect/cluster/entity/_handlers.py +168 -0
- pyfect/cluster/entity/_keep_alive.py +74 -0
- pyfect/cluster/entity/_queue.py +103 -0
- pyfect/cluster/entity/_request.py +34 -0
- pyfect/cluster/entity/_services.py +23 -0
- pyfect/cluster/entity/_test_client.py +237 -0
- pyfect/cluster/entity_address.py +25 -0
- pyfect/cluster/entity_id.py +20 -0
- pyfect/cluster/entity_proxy.py +161 -0
- pyfect/cluster/entity_proxy_server.py +103 -0
- pyfect/cluster/entity_resource.py +122 -0
- pyfect/cluster/entity_type.py +20 -0
- pyfect/cluster/envelope.py +307 -0
- pyfect/cluster/http_runner.py +254 -0
- pyfect/cluster/k8s_http_client.py +281 -0
- pyfect/cluster/k8s_types.py +1394 -0
- pyfect/cluster/machine_id.py +21 -0
- pyfect/cluster/message.py +219 -0
- pyfect/cluster/message_storage/__init__.py +67 -0
- pyfect/cluster/message_storage/_encoded.py +393 -0
- pyfect/cluster/message_storage/_memory.py +386 -0
- pyfect/cluster/message_storage/_noop.py +109 -0
- pyfect/cluster/message_storage/_service.py +388 -0
- pyfect/cluster/reply.py +236 -0
- pyfect/cluster/runner.py +32 -0
- pyfect/cluster/runner_address.py +23 -0
- pyfect/cluster/runner_health.py +93 -0
- pyfect/cluster/runner_server.py +286 -0
- pyfect/cluster/runner_storage.py +212 -0
- pyfect/cluster/runners/__init__.py +55 -0
- pyfect/cluster/runners/_local.py +460 -0
- pyfect/cluster/runners/_noop.py +57 -0
- pyfect/cluster/runners/_rpc.py +290 -0
- pyfect/cluster/runners/_service.py +113 -0
- pyfect/cluster/shard_id.py +39 -0
- pyfect/cluster/sharding/__init__.py +12 -0
- pyfect/cluster/sharding/_acquisition.py +490 -0
- pyfect/cluster/sharding/_clients.py +271 -0
- pyfect/cluster/sharding/_make.py +388 -0
- pyfect/cluster/sharding/_routing.py +296 -0
- pyfect/cluster/sharding/_runners_sync.py +198 -0
- pyfect/cluster/sharding/_service.py +91 -0
- pyfect/cluster/sharding/_singletons.py +183 -0
- pyfect/cluster/sharding/_state.py +296 -0
- pyfect/cluster/sharding/_storage_loop.py +421 -0
- pyfect/cluster/sharding_config.py +230 -0
- pyfect/cluster/sharding_registration_event.py +28 -0
- pyfect/cluster/single_runner.py +49 -0
- pyfect/cluster/singleton.py +36 -0
- pyfect/cluster/singleton_address.py +20 -0
- pyfect/cluster/snowflake.py +133 -0
- pyfect/cluster/socket_runner.py +67 -0
- pyfect/cluster/sql_message_storage.py +1001 -0
- pyfect/cluster/sql_runner_storage.py +841 -0
- pyfect/cluster/test_runner.py +23 -0
- pyfect/config/__init__.py +93 -0
- pyfect/config/_all.py +96 -0
- pyfect/config/_collections.py +291 -0
- pyfect/config/_config.py +261 -0
- pyfect/config/_constructors.py +106 -0
- pyfect/config/_error.py +27 -0
- pyfect/config/_leaf.py +92 -0
- pyfect/config/_scalars.py +193 -0
- pyfect/config/_schema.py +340 -0
- pyfect/config_provider/__init__.py +60 -0
- pyfect/config_provider/_dir.py +84 -0
- pyfect/config_provider/_dotenv.py +127 -0
- pyfect/config_provider/_env.py +79 -0
- pyfect/config_provider/_layers.py +80 -0
- pyfect/config_provider/_node.py +90 -0
- pyfect/config_provider/_provider.py +118 -0
- pyfect/config_provider/_unknown.py +62 -0
- pyfect/console/__init__.py +63 -0
- pyfect/console/_effects.py +134 -0
- pyfect/console/_live.py +156 -0
- pyfect/console/_service.py +49 -0
- pyfect/console/_table.py +90 -0
- pyfect/context.py +414 -0
- pyfect/cron/__init__.py +30 -0
- pyfect/cron/_model.py +183 -0
- pyfect/cron/_search.py +268 -0
- pyfect/cron/_text.py +202 -0
- pyfect/cron/_wall.py +42 -0
- pyfect/crypto.py +257 -0
- pyfect/date_time/__init__.py +77 -0
- pyfect/date_time/_calendar.py +209 -0
- pyfect/date_time/_current.py +118 -0
- pyfect/date_time/_epoch.py +22 -0
- pyfect/date_time/_iso.py +80 -0
- pyfect/date_time/_zones.py +135 -0
- pyfect/date_time/intl.py +144 -0
- pyfect/deferred.py +145 -0
- pyfect/devtools/__init__.py +24 -0
- pyfect/devtools/client.py +323 -0
- pyfect/devtools/layers.py +32 -0
- pyfect/devtools/schema.py +301 -0
- pyfect/devtools/server.py +72 -0
- pyfect/duration.py +295 -0
- pyfect/effect.py +565 -0
- pyfect/effectable.py +37 -0
- pyfect/encoding/__init__.py +62 -0
- pyfect/encoding/_archive.py +197 -0
- pyfect/encoding/_json_array_scanner.py +477 -0
- pyfect/encoding/base64.py +23 -0
- pyfect/encoding/base64_url.py +19 -0
- pyfect/encoding/encoding_error.py +17 -0
- pyfect/encoding/hex.py +28 -0
- pyfect/encoding/ini.py +188 -0
- pyfect/encoding/json_array.py +246 -0
- pyfect/encoding/ndjson.py +461 -0
- pyfect/encoding/schema_binary/__init__.py +257 -0
- pyfect/encoding/schema_binary/_channels.py +173 -0
- pyfect/encoding/schema_binary/_compile.py +584 -0
- pyfect/encoding/schema_binary/_decode.py +848 -0
- pyfect/encoding/schema_binary/_dictionary.py +90 -0
- pyfect/encoding/schema_binary/_encode.py +838 -0
- pyfect/encoding/schema_binary/_fingerprint.py +168 -0
- pyfect/encoding/schema_binary/_layout.py +350 -0
- pyfect/encoding/schema_binary/_literals.py +47 -0
- pyfect/encoding/schema_binary/_parser.py +163 -0
- pyfect/encoding/schema_binary/_reader.py +148 -0
- pyfect/encoding/schema_binary/_target.py +310 -0
- pyfect/encoding/schema_binary/_values.py +133 -0
- pyfect/encoding/schema_binary/_wire.py +253 -0
- pyfect/encoding/sse.py +476 -0
- pyfect/encoding/tar/__init__.py +396 -0
- pyfect/encoding/tar/_decoder.py +220 -0
- pyfect/encoding/tar/_encoder.py +83 -0
- pyfect/encoding/tar/_header.py +494 -0
- pyfect/encoding/tar/_model.py +82 -0
- pyfect/encoding/toml.py +375 -0
- pyfect/encoding/xml/__init__.py +222 -0
- pyfect/encoding/xml/_chars.py +119 -0
- pyfect/encoding/xml/_collector.py +106 -0
- pyfect/encoding/xml/_inputs.py +126 -0
- pyfect/encoding/xml/_model.py +114 -0
- pyfect/encoding/xml/_parser.py +694 -0
- pyfect/encoding/xml/_scanner.py +180 -0
- pyfect/encoding/xml/_writer.py +238 -0
- pyfect/encoding/yaml.py +739 -0
- pyfect/encoding/zip/__init__.py +392 -0
- pyfect/encoding/zip/_decoder.py +378 -0
- pyfect/encoding/zip/_format.py +122 -0
- pyfect/encoding/zip/_inflate.py +65 -0
- pyfect/encoding/zip/_model.py +86 -0
- pyfect/encoding/zip/_writer.py +311 -0
- pyfect/error_reporter.py +240 -0
- pyfect/errors.py +321 -0
- pyfect/eventlog/__init__.py +77 -0
- pyfect/eventlog/_conflicts.py +94 -0
- pyfect/eventlog/_crypto.py +64 -0
- pyfect/eventlog/_identity.py +26 -0
- pyfect/eventlog/_identity_derivation.py +79 -0
- pyfect/eventlog/_transferable_bytes.py +55 -0
- pyfect/eventlog/_uuid.py +33 -0
- pyfect/eventlog/event.py +115 -0
- pyfect/eventlog/event_group.py +55 -0
- pyfect/eventlog/event_journal.py +630 -0
- pyfect/eventlog/event_log.py +1039 -0
- pyfect/eventlog/event_log_encryption.py +219 -0
- pyfect/eventlog/event_log_message.py +357 -0
- pyfect/eventlog/event_log_remote.py +465 -0
- pyfect/eventlog/event_log_server.py +442 -0
- pyfect/eventlog/event_log_server_encrypted.py +322 -0
- pyfect/eventlog/event_log_server_unencrypted.py +814 -0
- pyfect/eventlog/event_log_session_auth.py +250 -0
- pyfect/eventlog/event_row.py +423 -0
- pyfect/eventlog/sql_event_journal.py +692 -0
- pyfect/eventlog/sql_event_log_server_encrypted.py +440 -0
- pyfect/eventlog/sql_event_log_server_unencrypted.py +568 -0
- pyfect/execution_plan/__init__.py +42 -0
- pyfect/execution_plan/_events.py +128 -0
- pyfect/execution_plan/_model.py +108 -0
- pyfect/execution_plan/_run.py +176 -0
- pyfect/exit.py +205 -0
- pyfect/fiber_handle.py +305 -0
- pyfect/fiber_map.py +350 -0
- pyfect/fiber_set.py +232 -0
- pyfect/file_system/__init__.py +69 -0
- pyfect/file_system/_copy.py +116 -0
- pyfect/file_system/_errors.py +32 -0
- pyfect/file_system/_file.py +177 -0
- pyfect/file_system/_live.py +351 -0
- pyfect/file_system/_model.py +72 -0
- pyfect/file_system/_noop.py +189 -0
- pyfect/file_system/_service.py +336 -0
- pyfect/file_system/_stat.py +69 -0
- pyfect/file_system/_watch.py +107 -0
- pyfect/filters.py +245 -0
- pyfect/graph/__init__.py +101 -0
- pyfect/graph/_all_pairs.py +150 -0
- pyfect/graph/_combine.py +259 -0
- pyfect/graph/_compact.py +143 -0
- pyfect/graph/_components.py +200 -0
- pyfect/graph/_cycles.py +144 -0
- pyfect/graph/_enumerate.py +168 -0
- pyfect/graph/_errors.py +51 -0
- pyfect/graph/_flow.py +164 -0
- pyfect/graph/_forest.py +109 -0
- pyfect/graph/_graph.py +269 -0
- pyfect/graph/_lowlink.py +98 -0
- pyfect/graph/_matching.py +101 -0
- pyfect/graph/_mutate.py +211 -0
- pyfect/graph/_query.py +138 -0
- pyfect/graph/_records.py +119 -0
- pyfect/graph/_render.py +109 -0
- pyfect/graph/_shortest.py +274 -0
- pyfect/graph/_snapshot.py +50 -0
- pyfect/graph/_store.py +136 -0
- pyfect/graph/_traverse.py +289 -0
- pyfect/graph/_view.py +384 -0
- pyfect/graph/_walker.py +70 -0
- pyfect/graphql/__init__.py +87 -0
- pyfect/graphql/_codec.py +117 -0
- pyfect/graphql/_http.py +309 -0
- pyfect/graphql/_schemas.py +39 -0
- pyfect/graphql/_slices.py +110 -0
- pyfect/graphql/_template.py +111 -0
- pyfect/graphql/_types.py +115 -0
- pyfect/graphql/_ws.py +395 -0
- pyfect/graphql/_ws_messages.py +120 -0
- pyfect/graphql/client.py +428 -0
- pyfect/graphql/client_error.py +285 -0
- pyfect/graphql/group.py +142 -0
- pyfect/graphql/middleware.py +98 -0
- pyfect/graphql/operation.py +409 -0
- pyfect/graphql/protocol.py +119 -0
- pyfect/graphql/resolver.py +574 -0
- pyfect/graphql/template.py +61 -0
- pyfect/hash_map.py +317 -0
- pyfect/hash_ring.py +206 -0
- pyfect/hash_set.py +163 -0
- pyfect/http/__init__.py +125 -0
- pyfect/http/_asgi_listener.py +272 -0
- pyfect/http/_authority.py +63 -0
- pyfect/http/_brotli.py +27 -0
- pyfect/http/_compression.py +52 -0
- pyfect/http/_field.py +18 -0
- pyfect/http/_foreign_path.py +27 -0
- pyfect/http/_form_data.py +34 -0
- pyfect/http/_httpx_options.py +120 -0
- pyfect/http/_in_memory.py +76 -0
- pyfect/http/_request_state.py +59 -0
- pyfect/http/_vary.py +14 -0
- pyfect/http/abort.py +65 -0
- pyfect/http/asgi/__init__.py +59 -0
- pyfect/http/asgi/_app.py +155 -0
- pyfect/http/asgi/_http.py +201 -0
- pyfect/http/asgi/_mount.py +263 -0
- pyfect/http/asgi/_scope.py +86 -0
- pyfect/http/asgi/_serve.py +72 -0
- pyfect/http/asgi/_types.py +13 -0
- pyfect/http/asgi/_web_socket.py +325 -0
- pyfect/http/body.py +437 -0
- pyfect/http/client/__init__.py +111 -0
- pyfect/http/client/_circuit_breaker.py +113 -0
- pyfect/http/client/_cookie_jar.py +120 -0
- pyfect/http/client/_make.py +252 -0
- pyfect/http/client/_rate_limit.py +423 -0
- pyfect/http/client/_redirects.py +93 -0
- pyfect/http/client/_retry.py +115 -0
- pyfect/http/client/_service.py +497 -0
- pyfect/http/cookies.py +444 -0
- pyfect/http/error.py +188 -0
- pyfect/http/etag.py +96 -0
- pyfect/http/file_like.py +49 -0
- pyfect/http/find_my_way/__init__.py +10 -0
- pyfect/http/find_my_way/_nodes.py +186 -0
- pyfect/http/find_my_way/_paths.py +43 -0
- pyfect/http/find_my_way/_query_string.py +67 -0
- pyfect/http/find_my_way/_router.py +401 -0
- pyfect/http/headers.py +343 -0
- pyfect/http/http_effect.py +198 -0
- pyfect/http/httpx_client.py +291 -0
- pyfect/http/hypercorn_server.py +122 -0
- pyfect/http/incoming_message.py +140 -0
- pyfect/http/live_server/__init__.py +191 -0
- pyfect/http/live_server/_body.py +164 -0
- pyfect/http/live_server/_exchange.py +321 -0
- pyfect/http/live_server/_head.py +194 -0
- pyfect/http/live_server/_inbound.py +156 -0
- pyfect/http/live_server/_listener.py +211 -0
- pyfect/http/live_server/_serve.py +225 -0
- pyfect/http/live_server/_timers.py +20 -0
- pyfect/http/live_server/_upgrade.py +108 -0
- pyfect/http/live_server/_websocket.py +286 -0
- pyfect/http/live_server/_ws_frames.py +242 -0
- pyfect/http/method.py +30 -0
- pyfect/http/middleware/__init__.py +61 -0
- pyfect/http/middleware/_compression.py +156 -0
- pyfect/http/middleware/_cors.py +129 -0
- pyfect/http/middleware/_decompression.py +274 -0
- pyfect/http/middleware/_forwarded.py +113 -0
- pyfect/http/middleware/_logger.py +73 -0
- pyfect/http/middleware/_search_params.py +34 -0
- pyfect/http/middleware/_tracer.py +153 -0
- pyfect/http/middleware/_types.py +16 -0
- pyfect/http/mime/__init__.py +69 -0
- pyfect/http/mime/_types.py +361 -0
- pyfect/http/multipart.py +613 -0
- pyfect/http/multipart_parser/__init__.py +348 -0
- pyfect/http/multipart_parser/_content_type.py +109 -0
- pyfect/http/multipart_parser/_headers.py +206 -0
- pyfect/http/multipart_parser/_search.py +85 -0
- pyfect/http/origin.py +146 -0
- pyfect/http/platform.py +593 -0
- pyfect/http/prometheus.py +34 -0
- pyfect/http/request.py +384 -0
- pyfect/http/response.py +474 -0
- pyfect/http/router/__init__.py +82 -0
- pyfect/http/router/_api.py +462 -0
- pyfect/http/router/_in_memory.py +76 -0
- pyfect/http/router/_markers.py +47 -0
- pyfect/http/router/_middleware.py +181 -0
- pyfect/http/router/_middleware_stack.py +51 -0
- pyfect/http/router/_provided.py +31 -0
- pyfect/http/router/_route.py +114 -0
- pyfect/http/router/_router.py +308 -0
- pyfect/http/schema.py +276 -0
- pyfect/http/server.py +199 -0
- pyfect/http/server_error.py +261 -0
- pyfect/http/server_request.py +729 -0
- pyfect/http/server_respondable.py +77 -0
- pyfect/http/server_response.py +569 -0
- pyfect/http/static_server/__init__.py +257 -0
- pyfect/http/static_server/_conditional.py +72 -0
- pyfect/http/static_server/_mime.py +55 -0
- pyfect/http/static_server/_range.py +53 -0
- pyfect/http/status.py +51 -0
- pyfect/http/template.py +161 -0
- pyfect/http/trace_context.py +91 -0
- pyfect/http/url.py +442 -0
- pyfect/http/url_params.py +304 -0
- pyfect/http/uvicorn_server.py +119 -0
- pyfect/http/web.py +171 -0
- pyfect/http/web_handler.py +463 -0
- pyfect/http/wire.py +209 -0
- pyfect/http_api/__init__.py +83 -0
- pyfect/http_api/_assets/scalar.js +8 -0
- pyfect/http_api/_assets/swagger-ui.css +3 -0
- pyfect/http_api/_assets/swagger-ui.js +4 -0
- pyfect/http_api/_client_method.py +220 -0
- pyfect/http_api/_client_payload.py +144 -0
- pyfect/http_api/_client_response.py +377 -0
- pyfect/http_api/_handlers.py +282 -0
- pyfect/http_api/_html.py +38 -0
- pyfect/http_api/_media_type.py +6 -0
- pyfect/http_api/_openapi_responses.py +164 -0
- pyfect/http_api/_openapi_schemas.py +163 -0
- pyfect/http_api/_path.py +102 -0
- pyfect/http_api/_payload.py +260 -0
- pyfect/http_api/_response.py +363 -0
- pyfect/http_api/_security.py +174 -0
- pyfect/http_api/api.py +265 -0
- pyfect/http_api/builder.py +447 -0
- pyfect/http_api/client.py +474 -0
- pyfect/http_api/endpoint.py +969 -0
- pyfect/http_api/error.py +280 -0
- pyfect/http_api/group.py +215 -0
- pyfect/http_api/middleware.py +232 -0
- pyfect/http_api/openapi.py +396 -0
- pyfect/http_api/scalar.py +143 -0
- pyfect/http_api/schema.py +713 -0
- pyfect/http_api/security.py +91 -0
- pyfect/http_api/swagger.py +69 -0
- pyfect/http_api/test.py +105 -0
- pyfect/json_patch.py +255 -0
- pyfect/json_pointer.py +100 -0
- pyfect/json_schema/__init__.py +66 -0
- pyfect/json_schema/_api.py +96 -0
- pyfect/json_schema/_converter.py +258 -0
- pyfect/json_schema/_draft04.py +198 -0
- pyfect/json_schema/_draft07.py +203 -0
- pyfect/json_schema/_keywords.py +202 -0
- pyfect/json_schema/_model.py +201 -0
- pyfect/json_schema/_openapi.py +208 -0
- pyfect/latch.py +127 -0
- pyfect/layer.py +479 -0
- pyfect/layer_map.py +279 -0
- pyfect/layer_ref.py +208 -0
- pyfect/logger/__init__.py +91 -0
- pyfect/logger/_batched.py +49 -0
- pyfect/logger/_console.py +116 -0
- pyfect/logger/_file.py +31 -0
- pyfect/logger/_formats.py +104 -0
- pyfect/logger/_layer.py +50 -0
- pyfect/logger/_level.py +53 -0
- pyfect/logger/_log.py +142 -0
- pyfect/logger/_model.py +163 -0
- pyfect/logger/_refs.py +78 -0
- pyfect/logger/_stdlib.py +54 -0
- pyfect/logger/_text.py +263 -0
- pyfect/logger/_tracer.py +35 -0
- pyfect/managed_runtime.py +393 -0
- pyfect/metric/__init__.py +119 -0
- pyfect/metric/_boundaries.py +27 -0
- pyfect/metric/_kinds.py +417 -0
- pyfect/metric/_metric.py +302 -0
- pyfect/metric/_prometheus.py +150 -0
- pyfect/metric/_refs.py +78 -0
- pyfect/metric/_runtime.py +75 -0
- pyfect/metric/_snapshot.py +97 -0
- pyfect/metric/_state.py +74 -0
- pyfect/msgspec.py +98 -0
- pyfect/mutable_hash_map.py +140 -0
- pyfect/mutable_hash_set.py +95 -0
- pyfect/net/__init__.py +18 -0
- pyfect/net/_errors.py +29 -0
- pyfect/net/address_resolver.py +179 -0
- pyfect/net/dns/__init__.py +92 -0
- pyfect/net/dns/_error.py +43 -0
- pyfect/net/dns/_records.py +335 -0
- pyfect/net/dns/_service.py +287 -0
- pyfect/net/dns/_system.py +89 -0
- pyfect/net/host.py +311 -0
- pyfect/net/ip_interface.py +202 -0
- pyfect/net/ip_network.py +250 -0
- pyfect/net/net_address/__init__.py +245 -0
- pyfect/net/net_address/_inet.py +412 -0
- pyfect/net/net_address/_ip.py +348 -0
- pyfect/net/net_address/_mac.py +119 -0
- pyfect/optic/__init__.py +50 -0
- pyfect/optic/_builtins.py +90 -0
- pyfect/optic/_data.py +97 -0
- pyfect/optic/_optic.py +422 -0
- pyfect/optic/_steps.py +151 -0
- pyfect/option.py +362 -0
- pyfect/otel/__init__.py +22 -0
- pyfect/otel/_attributes.py +67 -0
- pyfect/otel/_producer.py +322 -0
- pyfect/otel/_shutdown.py +26 -0
- pyfect/otel/_span.py +292 -0
- pyfect/otel/_wrapper.py +92 -0
- pyfect/otel/logger.py +154 -0
- pyfect/otel/metrics.py +126 -0
- pyfect/otel/resource.py +111 -0
- pyfect/otel/sdk.py +195 -0
- pyfect/otel/tracer.py +157 -0
- pyfect/partitioned_semaphore.py +211 -0
- pyfect/path.py +304 -0
- pyfect/persistence/__init__.py +77 -0
- pyfect/persistence/_columns.py +32 -0
- pyfect/persistence/_error.py +14 -0
- pyfect/persistence/_seconds.py +68 -0
- pyfect/persistence/_sql_keys.py +47 -0
- pyfect/persistence/circuit_breaker/__init__.py +120 -0
- pyfect/persistence/circuit_breaker/_breaker.py +317 -0
- pyfect/persistence/circuit_breaker/_errors.py +67 -0
- pyfect/persistence/circuit_breaker/_memory.py +243 -0
- pyfect/persistence/circuit_breaker/_redis.py +509 -0
- pyfect/persistence/circuit_breaker/_store.py +60 -0
- pyfect/persistence/circuit_breaker/_types.py +191 -0
- pyfect/persistence/key_value_store/__init__.py +51 -0
- pyfect/persistence/key_value_store/_layers.py +216 -0
- pyfect/persistence/key_value_store/_schema_store.py +91 -0
- pyfect/persistence/key_value_store/_sql.py +197 -0
- pyfect/persistence/key_value_store/_store.py +212 -0
- pyfect/persistence/persistable.py +164 -0
- pyfect/persistence/persisted_cache.py +159 -0
- pyfect/persistence/persisted_queue/__init__.py +75 -0
- pyfect/persistence/persisted_queue/_error.py +42 -0
- pyfect/persistence/persisted_queue/_mailbox.py +101 -0
- pyfect/persistence/persisted_queue/_memory.py +182 -0
- pyfect/persistence/persisted_queue/_queue.py +272 -0
- pyfect/persistence/persisted_queue/_redis.py +455 -0
- pyfect/persistence/persisted_queue/_redis_scripts.py +395 -0
- pyfect/persistence/persisted_queue/_sql.py +334 -0
- pyfect/persistence/persisted_queue/_sql_migrations.py +319 -0
- pyfect/persistence/persisted_queue/_sql_statements.py +441 -0
- pyfect/persistence/persisted_queue/_store.py +147 -0
- pyfect/persistence/persistence/__init__.py +84 -0
- pyfect/persistence/persistence/_kvs.py +135 -0
- pyfect/persistence/persistence/_memory.py +62 -0
- pyfect/persistence/persistence/_redis.py +162 -0
- pyfect/persistence/persistence/_service.py +276 -0
- pyfect/persistence/persistence/_sql.py +499 -0
- pyfect/persistence/rate_limiter/__init__.py +82 -0
- pyfect/persistence/rate_limiter/_errors.py +59 -0
- pyfect/persistence/rate_limiter/_limiter.py +325 -0
- pyfect/persistence/rate_limiter/_memory.py +217 -0
- pyfect/persistence/rate_limiter/_redis.py +485 -0
- pyfect/persistence/rate_limiter/_store.py +89 -0
- pyfect/persistence/redis/__init__.py +52 -0
- pyfect/persistence/redis/_error.py +12 -0
- pyfect/persistence/redis/_make.py +234 -0
- pyfect/persistence/redis/_script.py +78 -0
- pyfect/persistence/redis/_service.py +104 -0
- pyfect/persistence/redis_py.py +303 -0
- pyfect/persistence/user_directories.py +195 -0
- pyfect/platform.py +26 -0
- pyfect/platform_error.py +256 -0
- pyfect/pool/__init__.py +36 -0
- pyfect/pool/_pool.py +707 -0
- pyfect/pool/_state.py +161 -0
- pyfect/pool/_ttl.py +105 -0
- pyfect/pool/_waiters.py +116 -0
- pyfect/primary_key.py +35 -0
- pyfect/process/__init__.py +82 -0
- pyfect/process/_child.py +213 -0
- pyfect/process/_command.py +358 -0
- pyfect/process/_handle.py +77 -0
- pyfect/process/_live.py +455 -0
- pyfect/process/_os.py +78 -0
- pyfect/process/_pipes.py +291 -0
- pyfect/process/_spawner.py +75 -0
- pyfect/pubsub/__init__.py +33 -0
- pyfect/pubsub/_atomic.py +181 -0
- pyfect/pubsub/_hub.py +214 -0
- pyfect/pubsub/_pubsub.py +214 -0
- pyfect/pubsub/_replay.py +107 -0
- pyfect/pubsub/_subscription.py +226 -0
- pyfect/pull.py +132 -0
- pyfect/py.typed +0 -0
- pyfect/pydantic.py +188 -0
- pyfect/queue/__init__.py +33 -0
- pyfect/queue/_queue.py +525 -0
- pyfect/queue/_state.py +111 -0
- pyfect/queue/_views.py +55 -0
- pyfect/random.py +146 -0
- pyfect/rc_map.py +363 -0
- pyfect/rc_ref.py +271 -0
- pyfect/reactivity/__init__.py +53 -0
- pyfect/reactivity/_async_result_schema.py +227 -0
- pyfect/reactivity/_hydration.py +18 -0
- pyfect/reactivity/_live.py +21 -0
- pyfect/reactivity/_reactivity.py +256 -0
- pyfect/reactivity/_result_effect.py +17 -0
- pyfect/reactivity/_time.py +11 -0
- pyfect/reactivity/async_result.py +679 -0
- pyfect/reactivity/atom/__init__.py +143 -0
- pyfect/reactivity/atom/_combinators.py +223 -0
- pyfect/reactivity/atom/_constructors.py +80 -0
- pyfect/reactivity/atom/_conversions.py +79 -0
- pyfect/reactivity/atom/_effect.py +252 -0
- pyfect/reactivity/atom/_fn.py +341 -0
- pyfect/reactivity/atom/_kvs.py +101 -0
- pyfect/reactivity/atom/_optimistic.py +162 -0
- pyfect/reactivity/atom/_runtime.py +323 -0
- pyfect/reactivity/atom/_serializable.py +65 -0
- pyfect/reactivity/atom/_subscription_ref.py +102 -0
- pyfect/reactivity/atom/_swr.py +69 -0
- pyfect/reactivity/atom/_types.py +435 -0
- pyfect/reactivity/atom_http_api.py +667 -0
- pyfect/reactivity/atom_ref.py +260 -0
- pyfect/reactivity/atom_registry/__init__.py +219 -0
- pyfect/reactivity/atom_registry/_batch.py +96 -0
- pyfect/reactivity/atom_registry/_dispatcher.py +60 -0
- pyfect/reactivity/atom_registry/_lifetime.py +202 -0
- pyfect/reactivity/atom_registry/_node.py +423 -0
- pyfect/reactivity/atom_registry/_registry.py +380 -0
- pyfect/reactivity/atom_rpc.py +283 -0
- pyfect/reactivity/hydration.py +200 -0
- pyfect/redacted.py +83 -0
- pyfect/ref.py +129 -0
- pyfect/resource.py +98 -0
- pyfect/result.py +306 -0
- pyfect/rpc/__init__.py +64 -0
- pyfect/rpc/_encoded_schema.py +86 -0
- pyfect/rpc/_json_rpc.py +246 -0
- pyfect/rpc/_methods.py +177 -0
- pyfect/rpc/_rpc.py +498 -0
- pyfect/rpc/client/__init__.py +100 -0
- pyfect/rpc/client/_core.py +449 -0
- pyfect/rpc/client/_http.py +257 -0
- pyfect/rpc/client/_protocol.py +171 -0
- pyfect/rpc/client/_serialized.py +321 -0
- pyfect/rpc/client/_transports.py +328 -0
- pyfect/rpc/client/_worker.py +283 -0
- pyfect/rpc/client_error.py +35 -0
- pyfect/rpc/group.py +296 -0
- pyfect/rpc/message.py +299 -0
- pyfect/rpc/middleware.py +182 -0
- pyfect/rpc/schema.py +89 -0
- pyfect/rpc/serialization.py +366 -0
- pyfect/rpc/server/__init__.py +181 -0
- pyfect/rpc/server/_core.py +549 -0
- pyfect/rpc/server/_protocol.py +123 -0
- pyfect/rpc/server/_serialized.py +363 -0
- pyfect/rpc/server/_stdio.py +101 -0
- pyfect/rpc/server/_transports.py +427 -0
- pyfect/rpc/server/_worker.py +108 -0
- pyfect/rpc/test.py +74 -0
- pyfect/rpc/worker.py +92 -0
- pyfect/run.py +393 -0
- pyfect/schedule/__init__.py +91 -0
- pyfect/schedule/_combinators.py +168 -0
- pyfect/schedule/_constructors.py +223 -0
- pyfect/schedule/_cron.py +49 -0
- pyfect/schedule/_drive.py +191 -0
- pyfect/schedule/_model.py +405 -0
- pyfect/scheduler.py +575 -0
- pyfect/schema/__init__.py +866 -0
- pyfect/schema/_adapters.py +39 -0
- pyfect/schema/_arbitrary_constraint.py +242 -0
- pyfect/schema/_arbitrary_graph.py +325 -0
- pyfect/schema/_arbitrary_links.py +179 -0
- pyfect/schema/_arbitrary_number.py +112 -0
- pyfect/schema/_arrays.py +287 -0
- pyfect/schema/_assignable.py +118 -0
- pyfect/schema/_boolean_literals.py +31 -0
- pyfect/schema/_checks_number.py +216 -0
- pyfect/schema/_checks_order.py +140 -0
- pyfect/schema/_checks_size.py +277 -0
- pyfect/schema/_checks_string.py +241 -0
- pyfect/schema/_class.py +569 -0
- pyfect/schema/_codec_derivation.py +227 -0
- pyfect/schema/_codec_links.py +221 -0
- pyfect/schema/_codecs.py +316 -0
- pyfect/schema/_collections.py +172 -0
- pyfect/schema/_constructor_default.py +89 -0
- pyfect/schema/_data.py +440 -0
- pyfect/schema/_data_codecs.py +145 -0
- pyfect/schema/_decimal_regexp.py +243 -0
- pyfect/schema/_declare.py +68 -0
- pyfect/schema/_derive.py +292 -0
- pyfect/schema/_duration_codecs.py +27 -0
- pyfect/schema/_ecma_pattern.py +416 -0
- pyfect/schema/_error.py +26 -0
- pyfect/schema/_error_instance.py +222 -0
- pyfect/schema/_graph.py +271 -0
- pyfect/schema/_instances.py +240 -0
- pyfect/schema/_jsonschema/__init__.py +90 -0
- pyfect/schema/_jsonschema/_annotations.py +114 -0
- pyfect/schema/_jsonschema/_checks.py +253 -0
- pyfect/schema/_jsonschema/_compile.py +346 -0
- pyfect/schema/_jsonschema/_exact.py +19 -0
- pyfect/schema/_jsonschema/_merge.py +211 -0
- pyfect/schema/_jsonschema/_objects.py +231 -0
- pyfect/schema/_jsonschema/_references.py +221 -0
- pyfect/schema/_jsonschema/_template.py +69 -0
- pyfect/schema/_literals.py +147 -0
- pyfect/schema/_middleware.py +209 -0
- pyfect/schema/_net.py +522 -0
- pyfect/schema/_options.py +49 -0
- pyfect/schema/_parse.py +351 -0
- pyfect/schema/_parsed_text.py +37 -0
- pyfect/schema/_primitives.py +48 -0
- pyfect/schema/_record.py +41 -0
- pyfect/schema/_refine.py +81 -0
- pyfect/schema/_schema.py +158 -0
- pyfect/schema/_semver.py +144 -0
- pyfect/schema/_strategy/__init__.py +258 -0
- pyfect/schema/_strategy/_big_decimal.py +203 -0
- pyfect/schema/_strategy/_composites.py +119 -0
- pyfect/schema/_strategy/_leaves.py +94 -0
- pyfect/schema/_strategy/_net.py +133 -0
- pyfect/schema/_struct.py +87 -0
- pyfect/schema/_struct_keys.py +128 -0
- pyfect/schema/_tagged.py +254 -0
- pyfect/schema/_template_literal.py +172 -0
- pyfect/schema/_to_codec_array_from_single.py +115 -0
- pyfect/schema/_to_codec_go_json.py +174 -0
- pyfect/schema/_to_codec_json.py +73 -0
- pyfect/schema/_to_codec_string_tree.py +115 -0
- pyfect/schema/_to_differ_json_patch.py +44 -0
- pyfect/schema/_to_encoder_xml.py +119 -0
- pyfect/schema/_to_equivalence.py +186 -0
- pyfect/schema/_to_formatter.py +180 -0
- pyfect/schema/_to_iso.py +73 -0
- pyfect/schema/_to_strategy.py +31 -0
- pyfect/schema/_transform.py +210 -0
- pyfect/schema/_tree.py +27 -0
- pyfect/schema/_type_walk.py +53 -0
- pyfect/schema/_undefined.py +58 -0
- pyfect/schema/_value_codecs.py +84 -0
- pyfect/schema/_values.py +123 -0
- pyfect/schema/_void.py +86 -0
- pyfect/schema/ast/__init__.py +227 -0
- pyfect/schema/ast/_annotations.py +43 -0
- pyfect/schema/ast/_arrays.py +210 -0
- pyfect/schema/ast/_base.py +295 -0
- pyfect/schema/ast/_candidates.py +309 -0
- pyfect/schema/ast/_check.py +181 -0
- pyfect/schema/ast/_class.py +159 -0
- pyfect/schema/ast/_constructors.py +65 -0
- pyfect/schema/ast/_declaration.py +97 -0
- pyfect/schema/ast/_encoding.py +111 -0
- pyfect/schema/ast/_json.py +129 -0
- pyfect/schema/ast/_keywords.py +125 -0
- pyfect/schema/ast/_literal.py +157 -0
- pyfect/schema/ast/_objects.py +356 -0
- pyfect/schema/ast/_record.py +36 -0
- pyfect/schema/ast/_result.py +116 -0
- pyfect/schema/ast/_steps.py +209 -0
- pyfect/schema/ast/_suspend.py +65 -0
- pyfect/schema/ast/_template_literal.py +256 -0
- pyfect/schema/ast/_union.py +172 -0
- pyfect/schema/getter/__init__.py +136 -0
- pyfect/schema/getter/_binary.py +104 -0
- pyfect/schema/getter/_coerce.py +196 -0
- pyfect/schema/getter/_getter.py +243 -0
- pyfect/schema/getter/_make.py +151 -0
- pyfect/schema/getter/_text.py +141 -0
- pyfect/schema/getter/_tree_record.py +170 -0
- pyfect/schema/issue.py +420 -0
- pyfect/schema/model/__init__.py +120 -0
- pyfect/schema/model/_class.py +245 -0
- pyfect/schema/model/_dates.py +103 -0
- pyfect/schema/model/_fields.py +77 -0
- pyfect/schema/model/_toolkit.py +114 -0
- pyfect/schema/model/_uuid.py +69 -0
- pyfect/schema/representation/__init__.py +424 -0
- pyfect/schema/representation/_builtin_revivers.py +331 -0
- pyfect/schema/representation/_code.py +106 -0
- pyfect/schema/representation/_code_builtins.py +281 -0
- pyfect/schema/representation/_code_document.py +563 -0
- pyfect/schema/representation/_code_sort.py +111 -0
- pyfect/schema/representation/_from.py +278 -0
- pyfect/schema/representation/_import/__init__.py +78 -0
- pyfect/schema/representation/_import/_finalize.py +121 -0
- pyfect/schema/representation/_import/_intersect.py +467 -0
- pyfect/schema/representation/_import/_js_order.py +66 -0
- pyfect/schema/representation/_import/_keywords.py +224 -0
- pyfect/schema/representation/_import/_nodes.py +199 -0
- pyfect/schema/representation/_import/_translate.py +455 -0
- pyfect/schema/representation/_json.py +520 -0
- pyfect/schema/representation/_json_schema.py +178 -0
- pyfect/schema/representation/_model.py +284 -0
- pyfect/schema/representation/_revivers.py +89 -0
- pyfect/schema/representation/_to.py +249 -0
- pyfect/schema/transformation/__init__.py +99 -0
- pyfect/schema/transformation/_builtins.py +314 -0
- pyfect/schema/transformation/_durations.py +150 -0
- pyfect/schema/transformation/_transformation.py +177 -0
- pyfect/schema/variant_schema/__init__.py +69 -0
- pyfect/schema/variant_schema/_class.py +188 -0
- pyfect/schema/variant_schema/_defaults.py +30 -0
- pyfect/schema/variant_schema/_field.py +32 -0
- pyfect/schema/variant_schema/_struct.py +130 -0
- pyfect/schema/variant_schema/_toolkit.py +160 -0
- pyfect/scope.py +374 -0
- pyfect/scoped_ref.py +124 -0
- pyfect/semaphore.py +276 -0
- pyfect/semver.py +403 -0
- pyfect/semver_range.py +420 -0
- pyfect/sink/__init__.py +131 -0
- pyfect/sink/_combinators.py +151 -0
- pyfect/sink/_constructors.py +187 -0
- pyfect/sink/_context.py +87 -0
- pyfect/sink/_folds.py +336 -0
- pyfect/sink/_for_each.py +87 -0
- pyfect/sink/_sink.py +265 -0
- pyfect/sink/_steps.py +34 -0
- pyfect/sink/_taking.py +183 -0
- pyfect/socket/__init__.py +146 -0
- pyfect/socket/_accepted.py +65 -0
- pyfect/socket/_connection.py +384 -0
- pyfect/socket/_errors.py +135 -0
- pyfect/socket/_loop.py +66 -0
- pyfect/socket/_net.py +281 -0
- pyfect/socket/_socket.py +245 -0
- pyfect/socket/_tls.py +88 -0
- pyfect/socket/_tls_session.py +117 -0
- pyfect/socket/_web_socket.py +555 -0
- pyfect/socket/datagram/__init__.py +110 -0
- pyfect/socket/datagram/_core.py +460 -0
- pyfect/socket/datagram/_errors.py +102 -0
- pyfect/socket/datagram/_live.py +675 -0
- pyfect/socket/datagram/_model.py +217 -0
- pyfect/socket/server.py +258 -0
- pyfect/socket/ws.py +484 -0
- pyfect/sql/__init__.py +60 -0
- pyfect/sql/_loop.py +134 -0
- pyfect/sql/clickhouse/__init__.py +74 -0
- pyfect/sql/clickhouse/_connection.py +237 -0
- pyfect/sql/clickhouse/_rows.py +87 -0
- pyfect/sql/clickhouse/client.py +322 -0
- pyfect/sql/clickhouse/config.py +241 -0
- pyfect/sql/clickhouse/driver.py +434 -0
- pyfect/sql/clickhouse/error.py +106 -0
- pyfect/sql/clickhouse/migrator.py +47 -0
- pyfect/sql/clickhouse/params.py +168 -0
- pyfect/sql/client/__init__.py +35 -0
- pyfect/sql/client/_client.py +309 -0
- pyfect/sql/client/_sqlite_acquirers.py +130 -0
- pyfect/sql/client/_transaction.py +410 -0
- pyfect/sql/connection.py +87 -0
- pyfect/sql/d1/__init__.py +48 -0
- pyfect/sql/d1/api.py +270 -0
- pyfect/sql/d1/client.py +344 -0
- pyfect/sql/d1/config.py +55 -0
- pyfect/sql/error.py +311 -0
- pyfect/sql/libsql/__init__.py +58 -0
- pyfect/sql/libsql/_connection.py +110 -0
- pyfect/sql/libsql/_hrana.py +258 -0
- pyfect/sql/libsql/_local.py +251 -0
- pyfect/sql/libsql/_targets.py +28 -0
- pyfect/sql/libsql/_values.py +46 -0
- pyfect/sql/libsql/client.py +225 -0
- pyfect/sql/libsql/config.py +170 -0
- pyfect/sql/libsql/migrator.py +43 -0
- pyfect/sql/libsql/sdk.py +123 -0
- pyfect/sql/migrator.py +434 -0
- pyfect/sql/model.py +324 -0
- pyfect/sql/mssql/__init__.py +79 -0
- pyfect/sql/mssql/_connection.py +169 -0
- pyfect/sql/mssql/_driver.py +103 -0
- pyfect/sql/mssql/_names.py +9 -0
- pyfect/sql/mssql/_pool.py +125 -0
- pyfect/sql/mssql/_tds.py +447 -0
- pyfect/sql/mssql/_tds_params.py +155 -0
- pyfect/sql/mssql/_worker.py +155 -0
- pyfect/sql/mssql/client.py +344 -0
- pyfect/sql/mssql/config.py +118 -0
- pyfect/sql/mssql/error.py +111 -0
- pyfect/sql/mssql/migrator.py +43 -0
- pyfect/sql/mssql/parameter.py +44 -0
- pyfect/sql/mssql/procedure.py +119 -0
- pyfect/sql/mssql/types.py +137 -0
- pyfect/sql/mysql/__init__.py +60 -0
- pyfect/sql/mysql/_asyncmy.py +168 -0
- pyfect/sql/mysql/_bind.py +76 -0
- pyfect/sql/mysql/_connection.py +156 -0
- pyfect/sql/mysql/_pool.py +63 -0
- pyfect/sql/mysql/_session.py +262 -0
- pyfect/sql/mysql/client.py +244 -0
- pyfect/sql/mysql/config.py +108 -0
- pyfect/sql/mysql/error.py +104 -0
- pyfect/sql/mysql/migrator.py +44 -0
- pyfect/sql/pg/__init__.py +82 -0
- pyfect/sql/pg/_resolve.py +284 -0
- pyfect/sql/pg/_session.py +380 -0
- pyfect/sql/pg/client.py +398 -0
- pyfect/sql/pg/config.py +142 -0
- pyfect/sql/pg/connection.py +585 -0
- pyfect/sql/pg/error.py +131 -0
- pyfect/sql/pg/migrator.py +139 -0
- pyfect/sql/pg/pool.py +265 -0
- pyfect/sql/resolver.py +294 -0
- pyfect/sql/result.py +24 -0
- pyfect/sql/schema.py +116 -0
- pyfect/sql/sqlite/__init__.py +26 -0
- pyfect/sql/sqlite/_connection.py +230 -0
- pyfect/sql/sqlite/_worker.py +71 -0
- pyfect/sql/sqlite/client.py +248 -0
- pyfect/sql/sqlite/migrator.py +43 -0
- pyfect/sql/statement/__init__.py +144 -0
- pyfect/sql/statement/_compiler.py +384 -0
- pyfect/sql/statement/_segments.py +487 -0
- pyfect/sql/statement/_statement.py +526 -0
- pyfect/sql/statement/_transforms.py +93 -0
- pyfect/sql/stream.py +88 -0
- pyfect/stdio.py +206 -0
- pyfect/stream/__init__.py +151 -0
- pyfect/stream/_async_iterable.py +129 -0
- pyfect/stream/_base.py +69 -0
- pyfect/stream/_bridge_methods.py +139 -0
- pyfect/stream/_catch.py +135 -0
- pyfect/stream/_catch_reason.py +558 -0
- pyfect/stream/_channel_methods.py +61 -0
- pyfect/stream/_chunk_methods.py +169 -0
- pyfect/stream/_chunking.py +310 -0
- pyfect/stream/_concat.py +65 -0
- pyfect/stream/_constructors.py +341 -0
- pyfect/stream/_context.py +147 -0
- pyfect/stream/_error_methods.py +254 -0
- pyfect/stream/_execution_plan.py +199 -0
- pyfect/stream/_fanout.py +154 -0
- pyfect/stream/_fanout_methods.py +183 -0
- pyfect/stream/_filter_methods.py +203 -0
- pyfect/stream/_grouping.py +168 -0
- pyfect/stream/_many.py +65 -0
- pyfect/stream/_map_methods.py +233 -0
- pyfect/stream/_mapping.py +126 -0
- pyfect/stream/_run.py +114 -0
- pyfect/stream/_run_methods.py +114 -0
- pyfect/stream/_service_methods.py +36 -0
- pyfect/stream/_sink_methods.py +151 -0
- pyfect/stream/_sinking.py +277 -0
- pyfect/stream/_sources.py +124 -0
- pyfect/stream/_stream.py +162 -0
- pyfect/stream/_taking.py +330 -0
- pyfect/stream/_text.py +43 -0
- pyfect/stream/_timing.py +294 -0
- pyfect/stream/_timing_methods.py +252 -0
- pyfect/stream/_zip_methods.py +279 -0
- pyfect/stream/_zipping.py +213 -0
- pyfect/subscription_ref.py +66 -0
- pyfect/synchronized_ref.py +38 -0
- pyfect/take.py +36 -0
- pyfect/terminal/__init__.py +41 -0
- pyfect/terminal/_keys.py +113 -0
- pyfect/terminal/_live.py +223 -0
- pyfect/terminal/_service.py +73 -0
- pyfect/testing/__init__.py +122 -0
- pyfect/testing/_assertions.py +92 -0
- pyfect/testing/_clock.py +309 -0
- pyfect/testing/_console.py +164 -0
- pyfect/testing/_crypto.py +44 -0
- pyfect/testing/_decorators.py +211 -0
- pyfect/testing/_flaky.py +34 -0
- pyfect/testing/_layers.py +153 -0
- pyfect/testing/_prop.py +255 -0
- pyfect/testing/_runners.py +84 -0
- pyfect/testing/_runtime.py +174 -0
- pyfect/testing/_schema.py +238 -0
- pyfect/testing/plugin.py +59 -0
- pyfect/tracer/__init__.py +107 -0
- pyfect/tracer/_model.py +176 -0
- pyfect/tracer/_native.py +172 -0
- pyfect/tracer/_refs.py +87 -0
- pyfect/tracer/_spans.py +269 -0
- pyfect/transaction.py +340 -0
- pyfect/trie.py +317 -0
- pyfect/tx_chunk.py +127 -0
- pyfect/tx_deferred.py +75 -0
- pyfect/tx_hash_map.py +265 -0
- pyfect/tx_hash_set.py +151 -0
- pyfect/tx_priority_queue.py +224 -0
- pyfect/tx_pubsub.py +194 -0
- pyfect/tx_queue/__init__.py +27 -0
- pyfect/tx_queue/_queue.py +359 -0
- pyfect/tx_queue/_views.py +60 -0
- pyfect/tx_reentrant_lock.py +201 -0
- pyfect/tx_ref.py +79 -0
- pyfect/tx_semaphore.py +136 -0
- pyfect/tx_subscription_ref.py +118 -0
- pyfect/workers/__init__.py +62 -0
- pyfect/workers/_frames.py +82 -0
- pyfect/workers/_mailbox.py +51 -0
- pyfect/workers/_port.py +106 -0
- pyfect/workers/_process.py +155 -0
- pyfect/workers/_thread.py +100 -0
- pyfect/workers/python_worker.py +133 -0
- pyfect/workers/python_worker_runner.py +112 -0
- pyfect/workers/transferable.py +103 -0
- pyfect/workers/worker.py +273 -0
- pyfect/workers/worker_error.py +86 -0
- pyfect/workers/worker_runner.py +85 -0
- pyfect/workflow/__init__.py +78 -0
- pyfect/workflow/_crypto.py +23 -0
- pyfect/workflow/_encoded.py +381 -0
- pyfect/workflow/_memory.py +424 -0
- pyfect/workflow/_result.py +43 -0
- pyfect/workflow/_services.py +241 -0
- pyfect/workflow/_suspension.py +153 -0
- pyfect/workflow/activity.py +427 -0
- pyfect/workflow/durable_clock.py +72 -0
- pyfect/workflow/durable_deferred.py +403 -0
- pyfect/workflow/durable_queue.py +259 -0
- pyfect/workflow/workflow.py +509 -0
- pyfect/workflow/workflow_engine.py +42 -0
- pyfect/workflow/workflow_proxy.py +161 -0
- pyfect/workflow/workflow_proxy_server.py +146 -0
- pyfect_dev-0.1.0.dist-info/METADATA +296 -0
- pyfect_dev-0.1.0.dist-info/RECORD +1265 -0
- pyfect_dev-0.1.0.dist-info/WHEEL +4 -0
- pyfect_dev-0.1.0.dist-info/entry_points.txt +3 -0
- pyfect_dev-0.1.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"""What a revision lets a server ask its client (Effect's per-revision
|
|
2
|
+
``toReverseClient``), past the capability check every operation shares."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import assert_never, cast
|
|
8
|
+
|
|
9
|
+
from pyfect.ai.mcp_protocol import AnyProtocolAdapter
|
|
10
|
+
from pyfect.ai.mcp_schema import (
|
|
11
|
+
ClientCapabilities,
|
|
12
|
+
CreateMessageParams,
|
|
13
|
+
CreateMessageResult,
|
|
14
|
+
ElicitRequestParams,
|
|
15
|
+
JsonObject,
|
|
16
|
+
McpInputRequest,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
_TOOL_CONTENT = ("tool_use", "tool_result")
|
|
20
|
+
_CONTEXTS = ("thisServer", "allServers")
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _has_tool_content(content: object) -> bool:
|
|
24
|
+
blocks = cast("list[object]", content) if isinstance(content, list) else [content]
|
|
25
|
+
return any(
|
|
26
|
+
isinstance(block, dict) and cast("dict[str, object]", block).get("type") in _TOOL_CONTENT
|
|
27
|
+
for block in blocks
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _uses_tools(params: Mapping[str, object]) -> bool:
|
|
32
|
+
"""Assumes no shape, as the input-request check runs before any decoding."""
|
|
33
|
+
if "tools" in params or "toolChoice" in params:
|
|
34
|
+
return True
|
|
35
|
+
messages = params.get("messages")
|
|
36
|
+
if not isinstance(messages, list):
|
|
37
|
+
return False
|
|
38
|
+
return any(
|
|
39
|
+
isinstance(message, Mapping)
|
|
40
|
+
and _has_tool_content(cast("Mapping[str, object]", message).get("content"))
|
|
41
|
+
for message in cast("list[object]", messages)
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _input_fields(request: Mapping[str, object]) -> Mapping[str, object]:
|
|
46
|
+
"""``request.params`` as JavaScript reads a field of it: nothing or null
|
|
47
|
+
raises, anything else that is not an object has no fields."""
|
|
48
|
+
params = request["params"]
|
|
49
|
+
if params is None:
|
|
50
|
+
raise TypeError("input request params are null")
|
|
51
|
+
return cast("Mapping[str, object]", params) if isinstance(params, Mapping) else {}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def sampling_refusal(
|
|
55
|
+
protocol: AnyProtocolAdapter, capabilities: ClientCapabilities, params: CreateMessageParams
|
|
56
|
+
) -> str | None:
|
|
57
|
+
"""Why ``params`` cannot be sent, if so (Effect's per-revision
|
|
58
|
+
``createMessage``): 2025-11-25 gates tool use and included context on
|
|
59
|
+
their nested capabilities; before it, requests with either form have no
|
|
60
|
+
wire shape."""
|
|
61
|
+
uses_tools = _uses_tools(params)
|
|
62
|
+
if protocol.supports_icons:
|
|
63
|
+
sampling = capabilities.get("sampling") or {}
|
|
64
|
+
if uses_tools and sampling.get("tools") is None:
|
|
65
|
+
return "Client did not advertise the sampling.tools capability"
|
|
66
|
+
if params.get("includeContext") in _CONTEXTS and sampling.get("context") is None:
|
|
67
|
+
return "Client did not advertise the sampling.context capability"
|
|
68
|
+
return None
|
|
69
|
+
if uses_tools or any(
|
|
70
|
+
isinstance(m["content"], list)
|
|
71
|
+
or (
|
|
72
|
+
not protocol.supports_audio
|
|
73
|
+
and cast("dict[str, object]", m["content"]).get("type") == "audio"
|
|
74
|
+
)
|
|
75
|
+
for m in params["messages"]
|
|
76
|
+
):
|
|
77
|
+
return "Request is not representable by this protocol"
|
|
78
|
+
return None
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def sampling_result_refusal(
|
|
82
|
+
protocol: AnyProtocolAdapter, capabilities: ClientCapabilities, result: CreateMessageResult
|
|
83
|
+
) -> str | None:
|
|
84
|
+
"""2025-11-25: a client that did not advertise ``sampling.tools`` must
|
|
85
|
+
not answer with tool use."""
|
|
86
|
+
if not protocol.supports_icons:
|
|
87
|
+
return None
|
|
88
|
+
sampling = capabilities.get("sampling") or {}
|
|
89
|
+
uses_tools = result.get("stopReason") == "toolUse" or _has_tool_content(result["content"])
|
|
90
|
+
if uses_tools and sampling.get("tools") is None:
|
|
91
|
+
return "Client did not advertise the sampling.tools capability"
|
|
92
|
+
return None
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def project_elicitation(
|
|
96
|
+
protocol: AnyProtocolAdapter, capabilities: ClientCapabilities, params: ElicitRequestParams
|
|
97
|
+
) -> ElicitRequestParams | str:
|
|
98
|
+
"""``params`` as ``protocol`` sends them, or why it cannot (Effect's
|
|
99
|
+
per-revision ``elicit``): 2025-11-25 gates each mode on its nested
|
|
100
|
+
capability (an empty one is form support); 2025-06-18 has form mode
|
|
101
|
+
only, sent without its ``mode``."""
|
|
102
|
+
url = params.get("mode") == "url"
|
|
103
|
+
if protocol.supports_icons:
|
|
104
|
+
elicitation = capabilities.get("elicitation") or {}
|
|
105
|
+
supported = (
|
|
106
|
+
"url" in elicitation if url else "form" in elicitation or "url" not in elicitation
|
|
107
|
+
)
|
|
108
|
+
if not supported:
|
|
109
|
+
mode = "url" if url else "form"
|
|
110
|
+
return f"Client did not advertise the elicitation.{mode} capability"
|
|
111
|
+
return params
|
|
112
|
+
if url:
|
|
113
|
+
return "Request is not representable by this protocol"
|
|
114
|
+
return cast("ElicitRequestParams", {k: v for k, v in params.items() if k != "mode"})
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def required_input_capabilities(
|
|
118
|
+
requests: Mapping[str, McpInputRequest] | None, capabilities: ClientCapabilities
|
|
119
|
+
) -> dict[str, JsonObject]:
|
|
120
|
+
"""The client capabilities ``requests`` need that the client did not
|
|
121
|
+
declare (Effect's ``requiredCapabilitiesForInputRequests``). As there, it
|
|
122
|
+
sees a handler's requests before they are decoded, and raises on one it
|
|
123
|
+
cannot read."""
|
|
124
|
+
required: dict[str, JsonObject] = {}
|
|
125
|
+
for request in (requests or {}).values():
|
|
126
|
+
method = request["method"]
|
|
127
|
+
if method == "roots/list":
|
|
128
|
+
if capabilities.get("roots") is None:
|
|
129
|
+
required["roots"] = {}
|
|
130
|
+
elif method == "sampling/createMessage":
|
|
131
|
+
params = _input_fields(request)
|
|
132
|
+
advertised = capabilities.get("sampling")
|
|
133
|
+
sampling = required.get("sampling", {})
|
|
134
|
+
if _uses_tools(params) and (advertised or {}).get("tools") is None:
|
|
135
|
+
sampling["tools"] = {}
|
|
136
|
+
if (
|
|
137
|
+
params.get("includeContext") in _CONTEXTS
|
|
138
|
+
and (advertised or {}).get("context") is None
|
|
139
|
+
):
|
|
140
|
+
sampling["context"] = {}
|
|
141
|
+
if advertised is None or sampling:
|
|
142
|
+
required["sampling"] = sampling
|
|
143
|
+
elif method == "elicitation/create":
|
|
144
|
+
mode = "url" if _input_fields(request).get("mode") == "url" else "form"
|
|
145
|
+
elicitation = capabilities.get("elicitation")
|
|
146
|
+
supported = elicitation is not None and (
|
|
147
|
+
elicitation.get("url") is not None
|
|
148
|
+
if mode == "url"
|
|
149
|
+
else elicitation.get("form") is not None or not elicitation
|
|
150
|
+
)
|
|
151
|
+
if not supported:
|
|
152
|
+
required.setdefault("elicitation", {})[mode] = {}
|
|
153
|
+
else:
|
|
154
|
+
assert_never(method)
|
|
155
|
+
return required
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"""The value encoding of 2026-07-28's HTTP routing headers (``Mcp-Name``,
|
|
2
|
+
``Mcp-Param-*``)."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
import base64
|
|
7
|
+
import binascii
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
_BASE64_PREFIX = "=?base64?"
|
|
11
|
+
_BASE64_SUFFIX = "?="
|
|
12
|
+
_PLAIN_HEADER = re.compile(r"^[\t\x20-\x7e]*$")
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def decode_routing_header(value: str) -> str | None:
|
|
16
|
+
"""A routing header's value: plain visible ASCII as is, else UTF-8
|
|
17
|
+
Base64 between ``=?base64?`` and ``?=`` (``None`` when malformed)."""
|
|
18
|
+
if not (value.startswith(_BASE64_PREFIX) and value.endswith(_BASE64_SUFFIX)):
|
|
19
|
+
return value if _PLAIN_HEADER.match(value) else None
|
|
20
|
+
encoded = value[len(_BASE64_PREFIX) : -len(_BASE64_SUFFIX)]
|
|
21
|
+
try:
|
|
22
|
+
return base64.b64decode(encoded, validate=True).decode("utf-8")
|
|
23
|
+
except (binascii.Error, UnicodeDecodeError):
|
|
24
|
+
return None
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"""The Streamable HTTP transport's sessions, bounded.
|
|
2
|
+
|
|
3
|
+
Any peer the Origin check admits can open a session, so the table is
|
|
4
|
+
capped, a session idle past its timeout is dropped (each request renews
|
|
5
|
+
it; one serving a request is kept), and the message that opens it is read
|
|
6
|
+
under a size limit. Effect's table has none of these (see PARITY.md).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import math
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
from pyfect import byte_size
|
|
15
|
+
from pyfect._core import Effect
|
|
16
|
+
from pyfect.ai._mcp.core import McpServerService
|
|
17
|
+
from pyfect.ai._mcp.dispatch import Connection
|
|
18
|
+
from pyfect.byte_size import ByteSize
|
|
19
|
+
from pyfect.duration import Duration, Seconds, to_seconds
|
|
20
|
+
from pyfect.effect import fail, succeed
|
|
21
|
+
from pyfect.errors import IllegalArgumentError
|
|
22
|
+
|
|
23
|
+
DEFAULT_MAX_SESSIONS = 1000
|
|
24
|
+
DEFAULT_IDLE_TIMEOUT: Duration = 30 * 60
|
|
25
|
+
DEFAULT_MAX_MESSAGE_BYTES: byte_size.Input = "4 MiB"
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
@dataclass(frozen=True)
|
|
29
|
+
class Limits:
|
|
30
|
+
"""Parsed by ``limits``: every field is positive."""
|
|
31
|
+
|
|
32
|
+
max_sessions: int
|
|
33
|
+
idle_timeout: Seconds
|
|
34
|
+
max_message_bytes: ByteSize
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def limits(
|
|
38
|
+
max_sessions: int, idle_timeout: Duration, max_message_bytes: byte_size.Input
|
|
39
|
+
) -> Effect[Limits, IllegalArgumentError]:
|
|
40
|
+
if isinstance(max_sessions, bool) or max_sessions < 1:
|
|
41
|
+
return fail(IllegalArgumentError(f"max_sessions must be at least 1, got {max_sessions!r}"))
|
|
42
|
+
timeout = to_seconds(idle_timeout)
|
|
43
|
+
if not timeout > 0:
|
|
44
|
+
return fail(
|
|
45
|
+
IllegalArgumentError(f"session_idle_timeout must be positive, got {idle_timeout!r}")
|
|
46
|
+
)
|
|
47
|
+
size = byte_size.from_input(max_message_bytes)
|
|
48
|
+
if size is None or byte_size.is_zero(size):
|
|
49
|
+
return fail(
|
|
50
|
+
IllegalArgumentError(
|
|
51
|
+
f"max_message_bytes must be a positive byte size, got {max_message_bytes!r}"
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
return succeed(Limits(max_sessions, timeout, size))
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
@dataclass
|
|
58
|
+
class _Entry:
|
|
59
|
+
connection: Connection
|
|
60
|
+
last_used: float
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class Table:
|
|
64
|
+
"""Sessions by id. Times are the clock's monotonic seconds."""
|
|
65
|
+
|
|
66
|
+
def __init__(self, server: McpServerService, limits: Limits) -> None:
|
|
67
|
+
self._server = server
|
|
68
|
+
self.limits = limits
|
|
69
|
+
self._entries: dict[str, _Entry] = {}
|
|
70
|
+
|
|
71
|
+
def __len__(self) -> int:
|
|
72
|
+
return len(self._entries)
|
|
73
|
+
|
|
74
|
+
def _idle(self, entry: _Entry, now: float) -> bool:
|
|
75
|
+
timeout = self.limits.idle_timeout
|
|
76
|
+
return (
|
|
77
|
+
not math.isinf(timeout)
|
|
78
|
+
and now - entry.last_used > timeout
|
|
79
|
+
and not entry.connection.busy
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
def _drop(self, session_id: str) -> Connection:
|
|
83
|
+
connection = self._entries.pop(session_id).connection
|
|
84
|
+
self._server.disconnect(connection.client_id)
|
|
85
|
+
return connection
|
|
86
|
+
|
|
87
|
+
def sweep(self, now: float) -> list[Connection]:
|
|
88
|
+
"""Drop the idle sessions; the caller closes what is returned."""
|
|
89
|
+
idle = [id for id, entry in self._entries.items() if self._idle(entry, now)]
|
|
90
|
+
return [self._drop(id) for id in idle]
|
|
91
|
+
|
|
92
|
+
def full(self) -> bool:
|
|
93
|
+
return len(self._entries) >= self.limits.max_sessions
|
|
94
|
+
|
|
95
|
+
def open(self, session_id: str, connection: Connection, now: float) -> bool:
|
|
96
|
+
"""Add a session; ``False`` when the table is full."""
|
|
97
|
+
if self.full():
|
|
98
|
+
return False
|
|
99
|
+
self._entries[session_id] = _Entry(connection, now)
|
|
100
|
+
self._server.connect(connection)
|
|
101
|
+
return True
|
|
102
|
+
|
|
103
|
+
def get(self, session_id: str, now: float) -> tuple[Connection | None, Connection | None]:
|
|
104
|
+
"""The live session (renewed), and the idle one it replaced, to close."""
|
|
105
|
+
entry = self._entries.get(session_id)
|
|
106
|
+
if entry is None:
|
|
107
|
+
return None, None
|
|
108
|
+
if self._idle(entry, now):
|
|
109
|
+
return None, self._drop(session_id)
|
|
110
|
+
entry.last_used = now
|
|
111
|
+
return entry.connection, None
|
|
112
|
+
|
|
113
|
+
def pop(self, session_id: str) -> Connection | None:
|
|
114
|
+
return self._drop(session_id) if session_id in self._entries else None
|
pyfect/ai/_mcp/stdio.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"""The stdio transport: one client, newline-delimited JSON-RPC on standard
|
|
2
|
+
input and output."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from typing import Any, Never, cast
|
|
7
|
+
|
|
8
|
+
from pyfect import _json_text, stream
|
|
9
|
+
from pyfect._concurrency import for_each
|
|
10
|
+
from pyfect._core import Effect
|
|
11
|
+
from pyfect._fiber import Fiber
|
|
12
|
+
from pyfect._json_text import parse as parse_json_text
|
|
13
|
+
from pyfect.ai._mcp import batch
|
|
14
|
+
from pyfect.ai._mcp.core import McpServer
|
|
15
|
+
from pyfect.ai._mcp.dispatch import Connection, ServerInfo, error_response
|
|
16
|
+
from pyfect.ai.mcp_protocol import Registry
|
|
17
|
+
from pyfect.ai.mcp_schema import InvalidRequest, JsonObject, ParseError
|
|
18
|
+
from pyfect.effect import gen, provide_service, succeed, suspend, sync
|
|
19
|
+
from pyfect.errors import Done
|
|
20
|
+
from pyfect.logger import CurrentLogLevel, LogToStderr
|
|
21
|
+
from pyfect.platform_error import PlatformError
|
|
22
|
+
from pyfect.queue import Queue
|
|
23
|
+
from pyfect.stdio import Stdio
|
|
24
|
+
|
|
25
|
+
_VOID: Effect[None] = succeed(None)
|
|
26
|
+
_OUTPUT_BUFFER = 8
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _encode(message: JsonObject) -> str:
|
|
30
|
+
return _json_text.stringify(message) + "\n"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _await_batch(fiber: Fiber[None, Never]) -> Effect[object]:
|
|
34
|
+
return fiber.await_exit()
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def serve(info: ServerInfo, registry: Registry) -> Effect[None, PlatformError, McpServer | Stdio]:
|
|
38
|
+
"""Serve the client on standard input until it ends, then let the
|
|
39
|
+
requests still running finish. Standard output carries only the
|
|
40
|
+
protocol: the console loggers write to standard error (``LogToStderr``),
|
|
41
|
+
so a logged defect cannot corrupt it."""
|
|
42
|
+
|
|
43
|
+
@gen
|
|
44
|
+
def run():
|
|
45
|
+
server = yield from McpServer()
|
|
46
|
+
io = yield from Stdio()
|
|
47
|
+
# Bounded, as in Effect's stdio protocol: a client that stops reading
|
|
48
|
+
# holds up writers, so a subscription's backlog can overflow.
|
|
49
|
+
lines = Queue[str, Done](_OUTPUT_BUFFER)
|
|
50
|
+
|
|
51
|
+
def write(message: JsonObject) -> Effect[None]:
|
|
52
|
+
return lines.offer(_encode(message)).as_void()
|
|
53
|
+
|
|
54
|
+
batches: list[Fiber[None, Never]] = []
|
|
55
|
+
|
|
56
|
+
def write_batch(replies: list[JsonObject]) -> Effect[None]:
|
|
57
|
+
return lines.offer(_encode(cast("Any", replies))).as_void()
|
|
58
|
+
|
|
59
|
+
level = yield from CurrentLogLevel()
|
|
60
|
+
connection = Connection(
|
|
61
|
+
server,
|
|
62
|
+
info,
|
|
63
|
+
registry,
|
|
64
|
+
server.next_client_id(),
|
|
65
|
+
"stdio",
|
|
66
|
+
broadcast=write,
|
|
67
|
+
default_log_level=level,
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
def receive(line: str) -> Effect[None]:
|
|
71
|
+
if not line.strip():
|
|
72
|
+
return _VOID
|
|
73
|
+
try:
|
|
74
|
+
message: Any = parse_json_text(line)
|
|
75
|
+
except ValueError:
|
|
76
|
+
return write(error_response(None, ParseError("Parse error")))
|
|
77
|
+
if isinstance(message, list):
|
|
78
|
+
messages = cast("list[object]", message)
|
|
79
|
+
if not batch.accepts(connection, messages):
|
|
80
|
+
return write(
|
|
81
|
+
error_response(None, InvalidRequest("JSON-RPC batches are not supported"))
|
|
82
|
+
)
|
|
83
|
+
# On its own fiber: the batch may wait on answers still to be read.
|
|
84
|
+
answered = batch.answer(connection, messages, write).flat_map(
|
|
85
|
+
lambda replies: write_batch(replies) if replies else _VOID
|
|
86
|
+
)
|
|
87
|
+
return answered.fork_daemon().map(batches.append)
|
|
88
|
+
return connection.dispatch(message, write)
|
|
89
|
+
|
|
90
|
+
writer = yield from stream.from_queue(lines).run(io.stdout()).fork()
|
|
91
|
+
server.connect(connection)
|
|
92
|
+
reading = io.stdin.decode_text().split_lines().run_for_each(receive)
|
|
93
|
+
finished = suspend(lambda: for_each(list(batches), _await_batch)).as_void()
|
|
94
|
+
serving = yield from (
|
|
95
|
+
reading.and_then(connection.drain())
|
|
96
|
+
.and_then(finished)
|
|
97
|
+
.ensuring(
|
|
98
|
+
sync(lambda: server.disconnect(connection.client_id)).and_then(connection.close())
|
|
99
|
+
)
|
|
100
|
+
.fork()
|
|
101
|
+
)
|
|
102
|
+
# A dead writer would leave every writer waiting on the bounded output. Only a writer
|
|
103
|
+
# that ended early can interrupt anything: the output ends after serving has exited.
|
|
104
|
+
yield from writer.await_exit().and_then(serving.interrupt()).fork()
|
|
105
|
+
yield from serving.await_exit()
|
|
106
|
+
yield from lines.end()
|
|
107
|
+
yield from writer.join()
|
|
108
|
+
yield from serving.join()
|
|
109
|
+
|
|
110
|
+
return run.pipe(provide_service(LogToStderr, True))
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""2026-07-28's ``subscriptions/listen``: one request's stream of the list
|
|
2
|
+
changes and resource updates it asked for.
|
|
3
|
+
|
|
4
|
+
Events reach a listener through its own bounded backlog, so a slow client
|
|
5
|
+
holds up nobody else; one whose backlog overflows has its subscription ended,
|
|
6
|
+
after releasing the upstream listener even while its writes stay blocked.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from collections.abc import Callable
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from typing import Any, Never, cast
|
|
14
|
+
|
|
15
|
+
from pyfect._core import Effect
|
|
16
|
+
from pyfect.ai.mcp_schema import JsonObject, RequestId, SubscriptionFilter
|
|
17
|
+
from pyfect.deferred import Deferred
|
|
18
|
+
from pyfect.effect import gen, provide_service
|
|
19
|
+
from pyfect.exit import Success
|
|
20
|
+
from pyfect.pubsub import Subscription
|
|
21
|
+
from pyfect.queue import Queue
|
|
22
|
+
from pyfect.scope import Scope
|
|
23
|
+
|
|
24
|
+
MAX_PENDING_NOTIFICATIONS = 64
|
|
25
|
+
OVERFLOW_REASON = "Pending notification limit exceeded"
|
|
26
|
+
SUBSCRIPTION_ID_META = "io.modelcontextprotocol/subscriptionId"
|
|
27
|
+
ACKNOWLEDGED = "notifications/subscriptions/acknowledged"
|
|
28
|
+
UPDATED = "notifications/resources/updated"
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class Event:
|
|
33
|
+
"""A server notification a subscription may carry."""
|
|
34
|
+
|
|
35
|
+
method: str
|
|
36
|
+
params: JsonObject
|
|
37
|
+
list_change_revision: int | None = None
|
|
38
|
+
"""Set on a list change: a listener ignores those at or before the
|
|
39
|
+
revision it started at, even when their delivery was deferred."""
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
@dataclass(frozen=True, slots=True)
|
|
43
|
+
class Presence:
|
|
44
|
+
"""What the server has registered."""
|
|
45
|
+
|
|
46
|
+
tools: bool
|
|
47
|
+
resources: bool
|
|
48
|
+
prompts: bool
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _honored(requested: SubscriptionFilter, presence: Presence) -> SubscriptionFilter:
|
|
52
|
+
"""The part of ``requested`` the server can deliver."""
|
|
53
|
+
honored: SubscriptionFilter = {}
|
|
54
|
+
if presence.tools and requested.get("toolsListChanged") is True:
|
|
55
|
+
honored["toolsListChanged"] = True
|
|
56
|
+
if presence.prompts and requested.get("promptsListChanged") is True:
|
|
57
|
+
honored["promptsListChanged"] = True
|
|
58
|
+
if presence.resources and requested.get("resourcesListChanged") is True:
|
|
59
|
+
honored["resourcesListChanged"] = True
|
|
60
|
+
uris = requested.get("resourceSubscriptions")
|
|
61
|
+
if presence.resources and uris is not None:
|
|
62
|
+
honored["resourceSubscriptions"] = uris
|
|
63
|
+
return honored
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _wanted(event: Event, honored: SubscriptionFilter, uris: frozenset[str]) -> bool:
|
|
67
|
+
match event.method:
|
|
68
|
+
case "notifications/tools/list_changed":
|
|
69
|
+
return honored.get("toolsListChanged") is True
|
|
70
|
+
case "notifications/prompts/list_changed":
|
|
71
|
+
return honored.get("promptsListChanged") is True
|
|
72
|
+
case "notifications/resources/list_changed":
|
|
73
|
+
return honored.get("resourcesListChanged") is True
|
|
74
|
+
case _: # UPDATED
|
|
75
|
+
return event.params.get("uri") in uris
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def listen(
|
|
79
|
+
requested: SubscriptionFilter,
|
|
80
|
+
request_id: RequestId,
|
|
81
|
+
*,
|
|
82
|
+
presence: Presence,
|
|
83
|
+
list_change_revision: int,
|
|
84
|
+
subscribe: Effect[Subscription[Event], Never, Scope],
|
|
85
|
+
send: Callable[[str, JsonObject], Effect[None]],
|
|
86
|
+
terminate: Callable[[RequestId, str], Effect[None]],
|
|
87
|
+
) -> Effect[None, Never, Scope]:
|
|
88
|
+
"""Acknowledge ``requested``, then deliver its events through ``send``.
|
|
89
|
+
It runs until interrupted; it returns only after ``terminate`` ended a
|
|
90
|
+
subscription whose backlog overflowed."""
|
|
91
|
+
honored = _honored(requested, presence)
|
|
92
|
+
uris = frozenset(honored.get("resourceSubscriptions", ()))
|
|
93
|
+
identity: JsonObject = {SUBSCRIPTION_ID_META: request_id}
|
|
94
|
+
|
|
95
|
+
def project(event: Event) -> JsonObject:
|
|
96
|
+
meta = event.params.get("_meta")
|
|
97
|
+
# The server owns the subscription identifier, whatever the event carries.
|
|
98
|
+
params: JsonObject = {"_meta": {**(meta if isinstance(meta, dict) else {}), **identity}}
|
|
99
|
+
if event.method == UPDATED:
|
|
100
|
+
params["uri"] = event.params["uri"]
|
|
101
|
+
return params
|
|
102
|
+
|
|
103
|
+
@gen
|
|
104
|
+
def run():
|
|
105
|
+
outer = yield from Scope()
|
|
106
|
+
events_scope = yield from outer.fork()
|
|
107
|
+
events = yield from subscribe.pipe(provide_service(Scope, events_scope))
|
|
108
|
+
pending = Queue[tuple[str, JsonObject]](MAX_PENDING_NOTIFICATIONS, "dropping")
|
|
109
|
+
overflowed = Deferred[None]()
|
|
110
|
+
|
|
111
|
+
@gen
|
|
112
|
+
def watch():
|
|
113
|
+
while True:
|
|
114
|
+
event = yield from events.take()
|
|
115
|
+
revision = event.list_change_revision
|
|
116
|
+
if revision is not None and revision <= list_change_revision:
|
|
117
|
+
continue
|
|
118
|
+
if not _wanted(event, honored, uris):
|
|
119
|
+
continue
|
|
120
|
+
if not (yield from pending.offer((event.method, project(event)))):
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
# The upstream listener goes first, even while transport writes stay blocked.
|
|
124
|
+
yield from (
|
|
125
|
+
watch.ensuring(events_scope.close(Success(None)))
|
|
126
|
+
.and_then(overflowed.succeed(None))
|
|
127
|
+
.fork_scoped()
|
|
128
|
+
)
|
|
129
|
+
yield from send(ACKNOWLEDGED, {"_meta": identity, "notifications": cast("Any", honored)})
|
|
130
|
+
deliver = pending.take().flat_map(lambda notification: send(*notification)).forever()
|
|
131
|
+
yield from deliver.race(overflowed.wait())
|
|
132
|
+
yield from terminate(request_id, OVERFLOW_REASON)
|
|
133
|
+
|
|
134
|
+
return run
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""The JSON Schema of a tool's parameters or result as MCP lists it
|
|
2
|
+
(Effect's ``toolJsonSchema``): MCP wants an object at the root, so the
|
|
3
|
+
root is compiled inline even when it has an identifier; a recursive root
|
|
4
|
+
keeps its definition and the top-level ``$ref`` is resolved."""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from pyfect.ai._structured_output_json import resolve_root
|
|
9
|
+
from pyfect.json_schema import Document, JsonSchema
|
|
10
|
+
from pyfect.schema import Top
|
|
11
|
+
from pyfect.schema._jsonschema._annotations import Options, ReferenceCandidate
|
|
12
|
+
from pyfect.schema._jsonschema._compile import Compiler
|
|
13
|
+
from pyfect.schema._jsonschema._references import References
|
|
14
|
+
from pyfect.schema._to_codec_json import to_codec_json_asts
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def tool_json_schema(schema: Top, *, strict: bool) -> JsonSchema:
|
|
18
|
+
"""``schema``'s JSON Schema, objects closed when ``strict``, its
|
|
19
|
+
definitions under ``$defs``."""
|
|
20
|
+
[node] = to_codec_json_asts([schema.__codec__().ast])
|
|
21
|
+
root = References((), None).candidate(node).ast
|
|
22
|
+
|
|
23
|
+
def policy(candidate: ReferenceCandidate) -> str | None:
|
|
24
|
+
if candidate.ast is root and candidate.occurrences == 1:
|
|
25
|
+
return None
|
|
26
|
+
return candidate.identifier
|
|
27
|
+
|
|
28
|
+
compiler = Compiler(
|
|
29
|
+
References((node,), policy), Options("error" if strict else "ignore", False, None)
|
|
30
|
+
)
|
|
31
|
+
[out], definitions = compiler.finish([compiler.recur(node, [])])
|
|
32
|
+
resolved = dict(resolve_root(Document("draft-2020-12", out, definitions)))
|
|
33
|
+
if definitions:
|
|
34
|
+
resolved["$defs"] = dict(definitions)
|
|
35
|
+
return resolved
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
"""The provider answers of a ``choice`` / ``score`` / ``noul`` response,
|
|
2
|
+
the answer format Cloudflare's Clef and TypeSafe's System One share."""
|
|
3
|
+
|
|
4
|
+
from __future__ import annotations
|
|
5
|
+
|
|
6
|
+
from collections.abc import Mapping
|
|
7
|
+
from typing import Literal, TypedDict
|
|
8
|
+
|
|
9
|
+
from pyfect.ai import decision as D
|
|
10
|
+
from pyfect.ai import decision_model as DM
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ChoiceAnswer(TypedDict):
|
|
14
|
+
type: Literal["choice"]
|
|
15
|
+
choice: str
|
|
16
|
+
probabilities: Mapping[str, float]
|
|
17
|
+
confidence: float
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class ScoreAnswer(TypedDict):
|
|
21
|
+
type: Literal["score"]
|
|
22
|
+
score: float
|
|
23
|
+
probabilities: Mapping[str, float]
|
|
24
|
+
confidence: float
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NoulAnswer(TypedDict):
|
|
28
|
+
type: Literal["noul"]
|
|
29
|
+
noul: float
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
type Answer = ChoiceAnswer | ScoreAnswer | NoulAnswer
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def provider_answers(
|
|
36
|
+
decisions: Mapping[str, D.AnyDecision], answers: Mapping[str, Answer]
|
|
37
|
+
) -> dict[str, DM.ProviderAnswer]:
|
|
38
|
+
"""Each decision's answer; a score's probabilities are by level index,
|
|
39
|
+
so they are keyed by the levels' labels (an index with no level is
|
|
40
|
+
dropped)."""
|
|
41
|
+
found: dict[str, DM.ProviderAnswer] = {}
|
|
42
|
+
for key, decision in decisions.items():
|
|
43
|
+
answer = answers.get(key)
|
|
44
|
+
if answer is None:
|
|
45
|
+
continue
|
|
46
|
+
match answer["type"]:
|
|
47
|
+
case "choice":
|
|
48
|
+
found[key] = DM.ProviderClassifyAnswer(
|
|
49
|
+
answer["choice"], answer["probabilities"], answer["confidence"]
|
|
50
|
+
)
|
|
51
|
+
case "score":
|
|
52
|
+
levels = decision.criteria if isinstance(decision, D.Rate) else ()
|
|
53
|
+
by_index = answer["probabilities"]
|
|
54
|
+
probabilities = {
|
|
55
|
+
level.label: by_index[str(index)]
|
|
56
|
+
for index, level in enumerate(levels)
|
|
57
|
+
if str(index) in by_index
|
|
58
|
+
}
|
|
59
|
+
found[key] = DM.ProviderRateAnswer(
|
|
60
|
+
answer["score"], probabilities, answer["confidence"]
|
|
61
|
+
)
|
|
62
|
+
case "noul":
|
|
63
|
+
found[key] = DM.ProviderProbabilityAnswer(answer["noul"])
|
|
64
|
+
return found
|