graph-agents-cli 0.3.1__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.
- graph_agents_cli/__init__.py +26 -0
- graph_agents_cli/_api_policy.py +2145 -0
- graph_agents_cli/_approvals.py +400 -0
- graph_agents_cli/_build.py +186 -0
- graph_agents_cli/_build_info.json +7 -0
- graph_agents_cli/_chat_client.py +462 -0
- graph_agents_cli/_click.py +157 -0
- graph_agents_cli/_defaults.py +139 -0
- graph_agents_cli/_experiments.py +64 -0
- graph_agents_cli/_http.py +192 -0
- graph_agents_cli/_output.py +83 -0
- graph_agents_cli/_project.py +462 -0
- graph_agents_cli/_remote.py +220 -0
- graph_agents_cli/_response_schema.py +264 -0
- graph_agents_cli/_runner.py +319 -0
- graph_agents_cli/_skills_check.py +274 -0
- graph_agents_cli/_tools.py +189 -0
- graph_agents_cli/_trust.py +66 -0
- graph_agents_cli/api/__init__.py +15 -0
- graph_agents_cli/api/_changes.py +506 -0
- graph_agents_cli/api/_files.py +658 -0
- graph_agents_cli/api/cmd_api.py +2480 -0
- graph_agents_cli/deploy/__init__.py +15 -0
- graph_agents_cli/deploy/_config.py +171 -0
- graph_agents_cli/deploy/_image.py +128 -0
- graph_agents_cli/deploy/_kube.py +286 -0
- graph_agents_cli/deploy/_modes.py +234 -0
- graph_agents_cli/deploy/_preflight.py +370 -0
- graph_agents_cli/deploy/_values.py +168 -0
- graph_agents_cli/deploy/cmd_deploy.py +1866 -0
- graph_agents_cli/deploy/gitops.py +562 -0
- graph_agents_cli/deploy/local_load.py +273 -0
- graph_agents_cli/dev/__init__.py +13 -0
- graph_agents_cli/dev/cmd_build.py +131 -0
- graph_agents_cli/dev/cmd_install.py +78 -0
- graph_agents_cli/dev/cmd_lint.py +119 -0
- graph_agents_cli/dev/cmd_playground.py +297 -0
- graph_agents_cli/dev/policy_check.py +1287 -0
- graph_agents_cli/eval/__init__.py +22 -0
- graph_agents_cli/eval/_client.py +670 -0
- graph_agents_cli/eval/_common.py +177 -0
- graph_agents_cli/eval/_judge.py +168 -0
- graph_agents_cli/eval/_judge_runner.py +238 -0
- graph_agents_cli/eval/_paths.py +212 -0
- graph_agents_cli/eval/checks.py +581 -0
- graph_agents_cli/eval/cmd_analyze.py +278 -0
- graph_agents_cli/eval/cmd_compare.py +284 -0
- graph_agents_cli/eval/cmd_eval_group.py +80 -0
- graph_agents_cli/eval/cmd_generate.py +558 -0
- graph_agents_cli/eval/cmd_grade.py +466 -0
- graph_agents_cli/eval/cmd_metric.py +156 -0
- graph_agents_cli/eval/cmd_run.py +370 -0
- graph_agents_cli/eval/cmd_submit.py +400 -0
- graph_agents_cli/eval/config.py +435 -0
- graph_agents_cli/eval/dataset.py +350 -0
- graph_agents_cli/eval/gate.py +420 -0
- graph_agents_cli/eval/transcript.py +192 -0
- graph_agents_cli/extension/__init__.py +13 -0
- graph_agents_cli/extension/_compat.py +86 -0
- graph_agents_cli/extension/_loader.py +293 -0
- graph_agents_cli/extension/_manifest.py +135 -0
- graph_agents_cli/extension/_overrides.py +195 -0
- graph_agents_cli/extension/_paths.py +91 -0
- graph_agents_cli/extension/_refs.py +193 -0
- graph_agents_cli/extension/_resolver.py +453 -0
- graph_agents_cli/extension/_schema.py +106 -0
- graph_agents_cli/extension/_spec.py +253 -0
- graph_agents_cli/extension/_sync.py +102 -0
- graph_agents_cli/extension/_trust.py +58 -0
- graph_agents_cli/extension/cmd_extension_add.py +259 -0
- graph_agents_cli/extension/cmd_extension_group.py +57 -0
- graph_agents_cli/extension/cmd_extension_list.py +56 -0
- graph_agents_cli/extension/cmd_extension_remove.py +61 -0
- graph_agents_cli/extension/cmd_extension_update.py +195 -0
- graph_agents_cli/info/__init__.py +13 -0
- graph_agents_cli/info/cmd_info.py +222 -0
- graph_agents_cli/infra/__init__.py +15 -0
- graph_agents_cli/infra/checks.py +1169 -0
- graph_agents_cli/infra/cmd_infra.py +103 -0
- graph_agents_cli/main.py +591 -0
- graph_agents_cli/peer/__init__.py +15 -0
- graph_agents_cli/peer/_generate.py +254 -0
- graph_agents_cli/peer/cmd_peer.py +1151 -0
- graph_agents_cli/run/__init__.py +13 -0
- graph_agents_cli/run/_local_server.py +1157 -0
- graph_agents_cli/run/_signals.py +141 -0
- graph_agents_cli/run/cmd_approvals.py +530 -0
- graph_agents_cli/run/cmd_run.py +1421 -0
- graph_agents_cli/scaffold/__init__.py +19 -0
- graph_agents_cli/scaffold/agents/README.md +24 -0
- graph_agents_cli/scaffold/agents/empty_py/.template/templateconfig.yaml +22 -0
- graph_agents_cli/scaffold/agents/langgraph/.env.example +292 -0
- graph_agents_cli/scaffold/agents/langgraph/.template/templateconfig.yaml +28 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile +59 -0
- graph_agents_cli/scaffold/agents/langgraph/Dockerfile.langgraph-server +59 -0
- graph_agents_cli/scaffold/agents/langgraph/README.md +571 -0
- graph_agents_cli/scaffold/agents/langgraph/api-policy.yaml +60 -0
- graph_agents_cli/scaffold/agents/langgraph/app/__init__.py +20 -0
- graph_agents_cli/scaffold/agents/langgraph/app/agent.py +174 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/__init__.py +15 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a.py +2162 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/a2a_client.py +1167 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/api_client.py +4220 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/approvals.py +1349 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/auth.py +1986 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/chat.py +2962 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/checkpointer.py +432 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/content.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/db.py +580 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/limits.py +203 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/metrics.py +231 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/middleware.py +361 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/model.py +611 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/playground.py +230 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/run_locks.py +459 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/structured.py +755 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/telemetry.py +681 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/threads.py +493 -0
- graph_agents_cli/scaffold/agents/langgraph/app/app_utils/token_exchange.py +959 -0
- graph_agents_cli/scaffold/agents/langgraph/app/fast_api_app.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/__init__.py +55 -0
- graph_agents_cli/scaffold/agents/langgraph/app/policies/custom.py +97 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/__init__.py +46 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/example_api.py +92 -0
- graph_agents_cli/scaffold/agents/langgraph/app/tools/weather.py +33 -0
- graph_agents_cli/scaffold/agents/langgraph/langgraph.json +14 -0
- graph_agents_cli/scaffold/agents/langgraph/pyproject.toml +78 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/conftest.py +376 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/datasets/basic-dataset.json +53 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/eval/eval_config.yaml +32 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/approval_graph.py +137 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_issuer.py +216 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/fake_openai.py +357 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_outcomes.py +569 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_a2a_relay.py +479 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_api_surface.py +812 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals.py +1367 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_approvals_server.py +794 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor.py +497 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_cross_actor_server.py +247 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_history_repair.py +278 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_model_apis.py +242 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_postgres.py +637 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_resilience_postgres.py +770 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_runtime_guardrails.py +854 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_e2e.py +340 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_server_runtime.py +989 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_answers.py +584 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_structured_server.py +222 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/integration/test_token_exchange_issuer.py +650 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/.results/.placeholder +0 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/README.md +22 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/conftest.py +21 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/load_test/load_test.py +81 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_client.py +824 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_a2a_scoping.py +724 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client.py +1214 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_client_hardening.py +716 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_api_policy_rpc.py +767 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_approval_ledger.py +1536 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_fake_model.py +115 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_jwt_policy.py +991 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_limits.py +310 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging.py +148 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_logging_hardening.py +271 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_policy.py +378 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_resilience.py +610 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_server_auth.py +702 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_structured.py +673 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_telemetry.py +404 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_thread_listing.py +255 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_threads.py +268 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_token_exchange.py +1320 -0
- graph_agents_cli/scaffold/agents/langgraph/tests/unit/test_untrusted_content.py +393 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-fastapi.lock +2084 -0
- graph_agents_cli/scaffold/agents/langgraph/uv-langgraph-server.lock +2106 -0
- graph_agents_cli/scaffold/agents/langgraph/{{cookiecutter.agent_guidance_filename}} +129 -0
- graph_agents_cli/scaffold/base_templates/_shared/graph-agents-cli-manifest.yaml +36 -0
- graph_agents_cli/scaffold/base_templates/python/.dockerignore +32 -0
- graph_agents_cli/scaffold/base_templates/python/.github/CODEOWNERS +30 -0
- graph_agents_cli/scaffold/base_templates/python/.github/agent.env +7 -0
- graph_agents_cli/scaffold/base_templates/python/.github/workflows/pr_checks.yaml +214 -0
- graph_agents_cli/scaffold/base_templates/python/.gitignore +209 -0
- graph_agents_cli/scaffold/base_templates/python/tests/unit/test_dummy.py +23 -0
- graph_agents_cli/scaffold/base_templates/python/{{cookiecutter.agent_guidance_filename}} +35 -0
- graph_agents_cli/scaffold/cmd_scaffold_group.py +49 -0
- graph_agents_cli/scaffold/commands/__init__.py +13 -0
- graph_agents_cli/scaffold/commands/create.py +1424 -0
- graph_agents_cli/scaffold/commands/enhance.py +1652 -0
- graph_agents_cli/scaffold/commands/upgrade.py +570 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/agent.env +12 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/promote-to-prod.yaml +371 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/.github/workflows/staging.yaml +450 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-dev.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-prod.yaml +41 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/argocd/application-staging.yaml +43 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/.helmignore +14 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/Chart.yaml +21 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/examples/networkpolicy.yaml +103 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/NOTES.txt +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/_helpers.tpl +189 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/certificate.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/configmap.yaml +10 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/deployment.yaml +199 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/hpa.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/httproute.yaml +30 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/ingress.yaml +39 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/networkpolicy.yaml +48 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/pdb.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/postgresql-secret.yaml +37 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/service.yaml +15 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/serviceaccount.yaml +13 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/templates/servicemonitor.yaml +42 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-dev.yaml +22 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-prod.yaml +45 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values-staging.yaml +29 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/deployment/helm/{{cookiecutter.project_name}}/values.yaml +396 -0
- graph_agents_cli/scaffold/deployment_targets/kubernetes/python/tests/integration/test_chart.py +269 -0
- graph_agents_cli/scaffold/deployment_targets/none/README.md +5 -0
- graph_agents_cli/scaffold/deployment_targets/none/python/README.md +6 -0
- graph_agents_cli/scaffold/utils/__init__.py +13 -0
- graph_agents_cli/scaffold/utils/backup.py +212 -0
- graph_agents_cli/scaffold/utils/build_record.py +257 -0
- graph_agents_cli/scaffold/utils/cli_options.py +184 -0
- graph_agents_cli/scaffold/utils/fs.py +83 -0
- graph_agents_cli/scaffold/utils/generate_locks.py +214 -0
- graph_agents_cli/scaffold/utils/generation_metadata.py +88 -0
- graph_agents_cli/scaffold/utils/keyedit.py +768 -0
- graph_agents_cli/scaffold/utils/keymerge.py +537 -0
- graph_agents_cli/scaffold/utils/language.py +138 -0
- graph_agents_cli/scaffold/utils/lock_utils.py +94 -0
- graph_agents_cli/scaffold/utils/logging.py +77 -0
- graph_agents_cli/scaffold/utils/manifest.py +292 -0
- graph_agents_cli/scaffold/utils/merge.py +970 -0
- graph_agents_cli/scaffold/utils/merge3.py +216 -0
- graph_agents_cli/scaffold/utils/openapi_seed.py +199 -0
- graph_agents_cli/scaffold/utils/remote_template.py +376 -0
- graph_agents_cli/scaffold/utils/template.py +1352 -0
- graph_agents_cli/scaffold/utils/upgrade.py +894 -0
- graph_agents_cli/scaffold/utils/version.py +438 -0
- graph_agents_cli/secrets/__init__.py +15 -0
- graph_agents_cli/secrets/_apply.py +954 -0
- graph_agents_cli/secrets/_required.py +188 -0
- graph_agents_cli/secrets/cmd_secrets.py +211 -0
- graph_agents_cli/setup/__init__.py +13 -0
- graph_agents_cli/setup/_antigravity.py +221 -0
- graph_agents_cli/setup/cmd_auth.py +1030 -0
- graph_agents_cli/setup/cmd_dev_token.py +513 -0
- graph_agents_cli/setup/cmd_setup.py +428 -0
- graph_agents_cli/setup/cmd_update.py +140 -0
- graph_agents_cli/skills/__init__.py +13 -0
- graph_agents_cli/skills/_bundle.py +65 -0
- graph_agents_cli/skills/data/README.md +19 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/SKILL.md +357 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/github-settings.md +113 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/gitops.md +137 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/kubernetes.md +315 -0
- graph_agents_cli/skills/data/graph-agents-cli-deploy/references/secrets.md +160 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/SKILL.md +303 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/dataset_schema.md +282 -0
- graph_agents_cli/skills/data/graph-agents-cli-eval/references/metrics-guide.md +143 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/SKILL.md +659 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langchain-models.md +124 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/langgraph.md +235 -0
- graph_agents_cli/skills/data/graph-agents-cli-langgraph-code/references/template-contract.md +477 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/SKILL.md +231 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/langsmith.md +46 -0
- graph_agents_cli/skills/data/graph-agents-cli-observability/references/otel.md +59 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/SKILL.md +414 -0
- graph_agents_cli/skills/data/graph-agents-cli-scaffold/references/flags.md +134 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/SKILL.md +478 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/brainstorming.md +118 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/commands.md +419 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/extension.md +156 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/internals.md +67 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/spec-template.md +56 -0
- graph_agents_cli/skills/data/graph-agents-cli-workflow/references/terminology.md +119 -0
- graph_agents_cli/system/__init__.py +15 -0
- graph_agents_cli/system/_apply.py +519 -0
- graph_agents_cli/system/_checks.py +1023 -0
- graph_agents_cli/system/_deploy.py +215 -0
- graph_agents_cli/system/_model.py +363 -0
- graph_agents_cli/system/_system.py +664 -0
- graph_agents_cli/system/_views.py +208 -0
- graph_agents_cli/system/cmd_system.py +423 -0
- graph_agents_cli-0.3.1.dist-info/METADATA +162 -0
- graph_agents_cli-0.3.1.dist-info/RECORD +291 -0
- graph_agents_cli-0.3.1.dist-info/WHEEL +4 -0
- graph_agents_cli-0.3.1.dist-info/entry_points.txt +2 -0
- graph_agents_cli-0.3.1.dist-info/licenses/LICENSE +201 -0
- graph_agents_cli-0.3.1.dist-info/licenses/NOTICE +19 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Run guardrails and request limits, read from the environment.
|
|
16
|
+
|
|
17
|
+
Every limit has a default in code, so an unset variable never means
|
|
18
|
+
"unlimited". A value that does not parse (or is out of range) stops the app
|
|
19
|
+
at startup (`check_settings()`, called by the lifespan) instead of silently
|
|
20
|
+
falling back, so a typo cannot quietly change a limit. The getters read the
|
|
21
|
+
environment on every call, which keeps them easy to override in tests.
|
|
22
|
+
|
|
23
|
+
| Variable | Default | Meaning |
|
|
24
|
+
|----------------------------|---------|------------------------------------------------------|
|
|
25
|
+
| `RUN_TIMEOUT_S` | 300 | wall-clock limit of one run; the run is cancelled |
|
|
26
|
+
| `RECURSION_LIMIT` | 50 | LangGraph super-steps per run (model + tool steps) |
|
|
27
|
+
| `MAX_REQUEST_BYTES` | 1048576 | request body cap (413 above it) |
|
|
28
|
+
| `MAX_METADATA_KEYS` | 16 | keys in a `/chat` `metadata` object (422 above it) |
|
|
29
|
+
| `MAX_METADATA_VALUE_CHARS` | 256 | characters per metadata key and string value (422) |
|
|
30
|
+
| `SSE_HEARTBEAT_S` | 15 | idle seconds before a `: keep-alive` SSE comment |
|
|
31
|
+
| `RETENTION_DAYS` | 0 | purge threads idle longer than this (0 = keep all) |
|
|
32
|
+
|
|
33
|
+
A run of the template's agent needs two steps to answer at all and two more
|
|
34
|
+
for each tool call made after the previous one returned (the model step that
|
|
35
|
+
asks for it, then the tool step), so `RECURSION_LIMIT` = 50 leaves room for 24
|
|
36
|
+
sequential tool calls in one run (`sequential_tool_calls`). A run that
|
|
37
|
+
reaches it ends with a final message saying so (`message.end` status
|
|
38
|
+
`step_limit`) and keeps everything it did in the thread, so "continue" picks
|
|
39
|
+
up where it stopped; `chat.py` warns at startup when an API's
|
|
40
|
+
`limits.max_calls_per_run` cannot be reached within the limit.
|
|
41
|
+
|
|
42
|
+
Thread ids are 1-128 letters, digits or `_ . : -` (`THREAD_ID_PATTERN`).
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
from __future__ import annotations
|
|
46
|
+
|
|
47
|
+
import math
|
|
48
|
+
import os
|
|
49
|
+
import re
|
|
50
|
+
from collections.abc import Callable, Mapping
|
|
51
|
+
from typing import Any
|
|
52
|
+
|
|
53
|
+
RUN_TIMEOUT_S = ("RUN_TIMEOUT_S", 300.0)
|
|
54
|
+
RECURSION_LIMIT = ("RECURSION_LIMIT", 50)
|
|
55
|
+
MAX_REQUEST_BYTES = ("MAX_REQUEST_BYTES", 1_048_576)
|
|
56
|
+
MAX_METADATA_KEYS = ("MAX_METADATA_KEYS", 16)
|
|
57
|
+
MAX_METADATA_VALUE_CHARS = ("MAX_METADATA_VALUE_CHARS", 256)
|
|
58
|
+
SSE_HEARTBEAT_S = ("SSE_HEARTBEAT_S", 15.0)
|
|
59
|
+
RETENTION_DAYS = ("RETENTION_DAYS", 0)
|
|
60
|
+
|
|
61
|
+
MAX_THREAD_ID_CHARS = 128
|
|
62
|
+
# Letters, digits and `_ . : -`: safe in URLs, logs and SQL parameters alike.
|
|
63
|
+
THREAD_ID_PATTERN = "^[A-Za-z0-9_.:-]{1," + str(MAX_THREAD_ID_CHARS) + "}$"
|
|
64
|
+
_THREAD_ID = re.compile(THREAD_ID_PATTERN)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class SettingsError(ValueError):
|
|
68
|
+
"""A limit variable is set to something that is not a valid value."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _raw(name: str) -> str | None:
|
|
72
|
+
value = os.environ.get(name)
|
|
73
|
+
if value is None or not value.strip():
|
|
74
|
+
return None
|
|
75
|
+
return value.strip()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _int(spec: tuple[str, int], *, minimum: int) -> int:
|
|
79
|
+
name, default = spec
|
|
80
|
+
raw = _raw(name)
|
|
81
|
+
if raw is None:
|
|
82
|
+
return default
|
|
83
|
+
try:
|
|
84
|
+
value = int(raw)
|
|
85
|
+
except ValueError:
|
|
86
|
+
raise SettingsError(f"{name}={raw!r} is not an integer.") from None
|
|
87
|
+
if value < minimum:
|
|
88
|
+
raise SettingsError(f"{name}={value} must be >= {minimum}.")
|
|
89
|
+
return value
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _float(spec: tuple[str, float], *, minimum: float, exclusive: bool = True) -> float:
|
|
93
|
+
name, default = spec
|
|
94
|
+
raw = _raw(name)
|
|
95
|
+
if raw is None:
|
|
96
|
+
return default
|
|
97
|
+
try:
|
|
98
|
+
value = float(raw)
|
|
99
|
+
except ValueError:
|
|
100
|
+
raise SettingsError(f"{name}={raw!r} is not a number.") from None
|
|
101
|
+
if not math.isfinite(value) or value < minimum or (exclusive and value == minimum):
|
|
102
|
+
bound = ">" if exclusive else ">="
|
|
103
|
+
raise SettingsError(f"{name}={raw} must be a finite number {bound} {minimum}.")
|
|
104
|
+
return value
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def run_timeout_s() -> float:
|
|
108
|
+
"""Seconds one run may take before it is cancelled and recorded as `timeout`."""
|
|
109
|
+
return _float(RUN_TIMEOUT_S, minimum=0)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def recursion_limit() -> int:
|
|
113
|
+
"""LangGraph's per-run step limit (`GraphRecursionError` beyond it)."""
|
|
114
|
+
return _int(RECURSION_LIMIT, minimum=1)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def sequential_tool_calls(steps: int) -> int:
|
|
118
|
+
"""How many tool calls, one after another, fit in `steps` graph steps with a final answer."""
|
|
119
|
+
return max(0, (steps - 2) // 2)
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def steps_for_tool_calls(calls: int) -> int:
|
|
123
|
+
"""The `RECURSION_LIMIT` that fits `calls` sequential tool calls and a final answer."""
|
|
124
|
+
return 2 * calls + 2
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
def max_request_bytes() -> int:
|
|
128
|
+
return _int(MAX_REQUEST_BYTES, minimum=1)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def max_metadata_keys() -> int:
|
|
132
|
+
return _int(MAX_METADATA_KEYS, minimum=0)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def max_metadata_value_chars() -> int:
|
|
136
|
+
return _int(MAX_METADATA_VALUE_CHARS, minimum=1)
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def sse_heartbeat_s() -> float:
|
|
140
|
+
return _float(SSE_HEARTBEAT_S, minimum=0)
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def retention_days() -> int:
|
|
144
|
+
"""Days of inactivity after which a thread is purged; 0 keeps everything."""
|
|
145
|
+
return _int(RETENTION_DAYS, minimum=0)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
GETTERS: tuple[Callable[[], Any], ...] = (
|
|
149
|
+
run_timeout_s,
|
|
150
|
+
recursion_limit,
|
|
151
|
+
max_request_bytes,
|
|
152
|
+
max_metadata_keys,
|
|
153
|
+
max_metadata_value_chars,
|
|
154
|
+
sse_heartbeat_s,
|
|
155
|
+
retention_days,
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def check_settings(extra: tuple[Callable[[], Any], ...] = ()) -> None:
|
|
160
|
+
"""Parse every limit once; raise one `SettingsError` naming every bad variable."""
|
|
161
|
+
problems: list[str] = []
|
|
162
|
+
for getter in (*GETTERS, *extra):
|
|
163
|
+
try:
|
|
164
|
+
getter()
|
|
165
|
+
except SettingsError as exc:
|
|
166
|
+
problems.append(str(exc))
|
|
167
|
+
if problems:
|
|
168
|
+
raise SettingsError("Invalid settings: " + " ".join(problems))
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def valid_thread_id(thread_id: str) -> bool:
|
|
172
|
+
return isinstance(thread_id, str) and bool(_THREAD_ID.fullmatch(thread_id))
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def check_metadata(metadata: Mapping[str, Any]) -> dict[str, Any]:
|
|
176
|
+
"""Validate `/chat` client metadata against the caps; return it as a plain dict.
|
|
177
|
+
|
|
178
|
+
Metadata is a flat object of scalars (string, number, boolean or null),
|
|
179
|
+
at most `MAX_METADATA_KEYS` keys, keys and string values at most
|
|
180
|
+
`MAX_METADATA_VALUE_CHARS` characters. Anything else raises `ValueError`
|
|
181
|
+
(a 422 for the caller) instead of being dropped silently.
|
|
182
|
+
"""
|
|
183
|
+
max_keys = max_metadata_keys()
|
|
184
|
+
max_chars = max_metadata_value_chars()
|
|
185
|
+
if len(metadata) > max_keys:
|
|
186
|
+
raise ValueError(f"metadata has {len(metadata)} keys; at most {max_keys} are allowed.")
|
|
187
|
+
out: dict[str, Any] = {}
|
|
188
|
+
for key, value in metadata.items():
|
|
189
|
+
if not isinstance(key, str) or not key:
|
|
190
|
+
raise ValueError("metadata keys must be non-empty strings.")
|
|
191
|
+
if len(key) > max_chars:
|
|
192
|
+
raise ValueError(f"metadata key is longer than {max_chars} characters.")
|
|
193
|
+
if value is not None and not isinstance(value, str | int | float | bool):
|
|
194
|
+
raise ValueError(
|
|
195
|
+
f"metadata[{key[:40]!r}] must be a string, number, boolean or null "
|
|
196
|
+
"(nested objects and lists are not accepted)."
|
|
197
|
+
)
|
|
198
|
+
if isinstance(value, float) and not math.isfinite(value):
|
|
199
|
+
raise ValueError(f"metadata[{key[:40]!r}] must be a finite number.")
|
|
200
|
+
if isinstance(value, str) and len(value) > max_chars:
|
|
201
|
+
raise ValueError(f"metadata[{key[:40]!r}] is longer than {max_chars} characters.")
|
|
202
|
+
out[key] = value
|
|
203
|
+
return out
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Copyright 2026 graph-agents-cli contributors
|
|
2
|
+
#
|
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
# you may not use this file except in compliance with the License.
|
|
5
|
+
# You may obtain a copy of the License at
|
|
6
|
+
#
|
|
7
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
#
|
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
# See the License for the specific language governing permissions and
|
|
13
|
+
# limitations under the License.
|
|
14
|
+
|
|
15
|
+
"""Prometheus metrics served at `GET /metrics` (`METRICS_ENABLED`, default true).
|
|
16
|
+
|
|
17
|
+
`METRICS_TOKEN` (optional, a secret): when set, `/metrics` answers only
|
|
18
|
+
requests carrying `Authorization: Bearer <METRICS_TOKEN>` (401 otherwise), as
|
|
19
|
+
defence in depth behind keeping the endpoint off the public ingress. `/health`
|
|
20
|
+
and `/ready` stay open for the probes. Under langgraph-server this is the
|
|
21
|
+
`/metrics` of the server image, which turns the server's own meta routes off
|
|
22
|
+
(`disable_meta`); `langgraph dev` keeps the server's `/metrics` in front of it.
|
|
23
|
+
|
|
24
|
+
Per process (scrape every replica):
|
|
25
|
+
|
|
26
|
+
* `http_requests_total{method, route, status}` and
|
|
27
|
+
`http_request_duration_seconds{method, route}`: every HTTP request, labelled
|
|
28
|
+
by the route template (`/threads/{thread_id}/messages`), never the raw path;
|
|
29
|
+
a streamed `/chat` counts until its last event.
|
|
30
|
+
* `agent_runs_total{status}`: finished runs by status (`ok`, `step_limit`,
|
|
31
|
+
`error`, `timeout`, `cancelled`, `interrupted`). `interrupted` counts the
|
|
32
|
+
runs this replica stopped because it lost its lease on the thread, plus the
|
|
33
|
+
runs of dead processes it found and closed (each counted once across the
|
|
34
|
+
replicas, see `RunStore.reconcile`); `agent_active_runs`: runs in progress;
|
|
35
|
+
`agent_run_duration_seconds`; `agent_tokens_total{kind}` (input/output).
|
|
36
|
+
* `agent_database_up`: 1 while the database answered at last contact, 0 while
|
|
37
|
+
it is known to be unreachable (always 1 without a database).
|
|
38
|
+
* `agent_approvals_total{event}`: human approvals of gated API calls:
|
|
39
|
+
`requested` (a run paused for one), `approved`, `rejected`, `expired` (a
|
|
40
|
+
pending approval that reached its `timeout_s`, or was superseded). A run
|
|
41
|
+
that pauses ends with status `awaiting_approval` in `agent_runs_total`.
|
|
42
|
+
* `agent_token_exchanges_total{api, outcome}`: tokens asked for `auth: exchange`
|
|
43
|
+
APIs (`token_exchange.py`) by outcome: `issued` (the issuer minted one),
|
|
44
|
+
`cached` (a kept one, or one another call was exchanging), `refused` (the
|
|
45
|
+
issuer refused, or refused within `TOKEN_EXCHANGE_FAILURE_TTL_S`),
|
|
46
|
+
`no_actor` (the issued token names no actor and the API does not set
|
|
47
|
+
`exchange.allow_actorless`: this agent refused it, and remembers that for
|
|
48
|
+
`TOKEN_EXCHANGE_FAILURE_TTL_S`), `unavailable` (a timeout, connection
|
|
49
|
+
error, 5xx or unusable answer) and
|
|
50
|
+
`circuit_open` (failed at once while the issuer's breaker is open);
|
|
51
|
+
`agent_token_exchange_duration_seconds{api}`: the exchanges sent. `api` is
|
|
52
|
+
the API's name in api-policy.yaml.
|
|
53
|
+
|
|
54
|
+
Metrics live in a registry of their own, so nothing else in the process
|
|
55
|
+
(libraries, a reloaded module) can add or duplicate series. Labels never carry
|
|
56
|
+
ids, principals or client input.
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
from __future__ import annotations
|
|
60
|
+
|
|
61
|
+
import hmac
|
|
62
|
+
import os
|
|
63
|
+
from typing import Any
|
|
64
|
+
|
|
65
|
+
from prometheus_client import (
|
|
66
|
+
CONTENT_TYPE_LATEST,
|
|
67
|
+
CollectorRegistry,
|
|
68
|
+
Counter,
|
|
69
|
+
Gauge,
|
|
70
|
+
Histogram,
|
|
71
|
+
generate_latest,
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
from {{cookiecutter.agent_directory}}.app_utils.limits import SettingsError
|
|
75
|
+
|
|
76
|
+
REGISTRY = CollectorRegistry(auto_describe=True)
|
|
77
|
+
|
|
78
|
+
HTTP_REQUESTS = Counter(
|
|
79
|
+
"http_requests_total",
|
|
80
|
+
"HTTP requests by method, route template and status code.",
|
|
81
|
+
["method", "route", "status"],
|
|
82
|
+
registry=REGISTRY,
|
|
83
|
+
)
|
|
84
|
+
HTTP_LATENCY = Histogram(
|
|
85
|
+
"http_request_duration_seconds",
|
|
86
|
+
"HTTP request duration (a streamed response counts until its last byte).",
|
|
87
|
+
["method", "route"],
|
|
88
|
+
registry=REGISTRY,
|
|
89
|
+
)
|
|
90
|
+
RUNS = Counter(
|
|
91
|
+
"agent_runs_total",
|
|
92
|
+
"Finished agent runs by status (ok, awaiting_approval, step_limit, error, timeout, "
|
|
93
|
+
"cancelled, interrupted).",
|
|
94
|
+
["status"],
|
|
95
|
+
registry=REGISTRY,
|
|
96
|
+
)
|
|
97
|
+
APPROVAL_EVENTS = ("requested", "approved", "rejected", "expired")
|
|
98
|
+
APPROVALS = Counter(
|
|
99
|
+
"agent_approvals_total",
|
|
100
|
+
"Human approvals of gated API calls by event (requested, approved, rejected, expired).",
|
|
101
|
+
["event"],
|
|
102
|
+
registry=REGISTRY,
|
|
103
|
+
)
|
|
104
|
+
for _event in APPROVAL_EVENTS:
|
|
105
|
+
APPROVALS.labels(_event) # every series exists from the start, at 0
|
|
106
|
+
TOKEN_EXCHANGE_OUTCOMES = (
|
|
107
|
+
"issued",
|
|
108
|
+
"cached",
|
|
109
|
+
"refused",
|
|
110
|
+
"no_actor",
|
|
111
|
+
"unavailable",
|
|
112
|
+
"circuit_open",
|
|
113
|
+
)
|
|
114
|
+
TOKEN_EXCHANGES = Counter(
|
|
115
|
+
"agent_token_exchanges_total",
|
|
116
|
+
"Tokens asked for auth: exchange APIs by API and outcome (issued, cached, refused, "
|
|
117
|
+
"no_actor, unavailable, circuit_open).",
|
|
118
|
+
["api", "outcome"],
|
|
119
|
+
registry=REGISTRY,
|
|
120
|
+
)
|
|
121
|
+
TOKEN_EXCHANGE_LATENCY = Histogram(
|
|
122
|
+
"agent_token_exchange_duration_seconds",
|
|
123
|
+
"Token exchanges sent to the issuer, by API.",
|
|
124
|
+
["api"],
|
|
125
|
+
buckets=(0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2, 5),
|
|
126
|
+
registry=REGISTRY,
|
|
127
|
+
)
|
|
128
|
+
ACTIVE_RUNS = Gauge("agent_active_runs", "Agent runs in progress.", registry=REGISTRY)
|
|
129
|
+
DATABASE_UP = Gauge(
|
|
130
|
+
"agent_database_up",
|
|
131
|
+
"1 while the database answered at last contact, 0 while it is known to be unreachable.",
|
|
132
|
+
registry=REGISTRY,
|
|
133
|
+
)
|
|
134
|
+
DATABASE_UP.set(1)
|
|
135
|
+
RUN_LATENCY = Histogram(
|
|
136
|
+
"agent_run_duration_seconds",
|
|
137
|
+
"Agent run duration.",
|
|
138
|
+
buckets=(0.25, 0.5, 1, 2, 5, 10, 20, 30, 60, 120, 300, 600),
|
|
139
|
+
registry=REGISTRY,
|
|
140
|
+
)
|
|
141
|
+
TOKENS = Counter(
|
|
142
|
+
"agent_tokens_total",
|
|
143
|
+
"Model tokens used by agent runs.",
|
|
144
|
+
["kind"],
|
|
145
|
+
registry=REGISTRY,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
_TRUE = ("1", "true", "yes", "on")
|
|
149
|
+
_FALSE = ("0", "false", "no", "off")
|
|
150
|
+
_METHODS = frozenset({"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"})
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def metrics_enabled() -> bool:
|
|
154
|
+
raw = (os.environ.get("METRICS_ENABLED") or "true").strip().lower()
|
|
155
|
+
if raw in _TRUE:
|
|
156
|
+
return True
|
|
157
|
+
if raw in _FALSE:
|
|
158
|
+
return False
|
|
159
|
+
raise SettingsError(f"METRICS_ENABLED={raw!r} must be true or false.")
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def metrics_token() -> str | None:
|
|
163
|
+
"""`METRICS_TOKEN`, or None when unset or blank (then `/metrics` needs no token)."""
|
|
164
|
+
token = (os.environ.get("METRICS_TOKEN") or "").strip()
|
|
165
|
+
return token or None
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def metrics_authorized(authorization: str | None) -> bool:
|
|
169
|
+
"""True when no token is configured or `authorization` is `Bearer <METRICS_TOKEN>`."""
|
|
170
|
+
token = metrics_token()
|
|
171
|
+
if token is None:
|
|
172
|
+
return True
|
|
173
|
+
scheme, _, presented = (authorization or "").partition(" ")
|
|
174
|
+
presented = presented.strip()
|
|
175
|
+
return (
|
|
176
|
+
scheme.lower() == "bearer"
|
|
177
|
+
and bool(presented)
|
|
178
|
+
and hmac.compare_digest(presented.encode("utf-8"), token.encode("utf-8"))
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def render() -> tuple[bytes, str]:
|
|
183
|
+
"""The exposition text and its content type."""
|
|
184
|
+
return generate_latest(REGISTRY), CONTENT_TYPE_LATEST
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def route_label(scope: dict[str, Any]) -> str:
|
|
188
|
+
"""The matched route's template, or `unmatched` (a 404 must not mint a series per path)."""
|
|
189
|
+
route = scope.get("route")
|
|
190
|
+
path = getattr(route, "path_format", None) or getattr(route, "path", None)
|
|
191
|
+
return str(path) if path else "unmatched"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def observe_request(method: str, route: str, status: int, seconds: float) -> None:
|
|
195
|
+
method = method if method in _METHODS else "OTHER"
|
|
196
|
+
HTTP_REQUESTS.labels(method, route, str(status)).inc()
|
|
197
|
+
HTTP_LATENCY.labels(method, route).observe(seconds)
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def observe_run(status: str, seconds: float, input_tokens: int, output_tokens: int) -> None:
|
|
201
|
+
RUNS.labels(status).inc()
|
|
202
|
+
RUN_LATENCY.observe(seconds)
|
|
203
|
+
if input_tokens:
|
|
204
|
+
TOKENS.labels("input").inc(input_tokens)
|
|
205
|
+
if output_tokens:
|
|
206
|
+
TOKENS.labels("output").inc(output_tokens)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def observe_interrupted_runs(count: int) -> None:
|
|
210
|
+
"""Runs of a dead process, found `running` and closed as `interrupted`."""
|
|
211
|
+
if count:
|
|
212
|
+
RUNS.labels("interrupted").inc(count)
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def observe_database(up: bool) -> None:
|
|
216
|
+
DATABASE_UP.set(1 if up else 0)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def observe_approvals(event: str, count: int = 1) -> None:
|
|
220
|
+
"""`count` approvals `requested`, `approved`, `rejected` or `expired`."""
|
|
221
|
+
if count and event in APPROVAL_EVENTS:
|
|
222
|
+
APPROVALS.labels(event).inc(count)
|
|
223
|
+
|
|
224
|
+
|
|
225
|
+
def observe_token_exchange(api: str, outcome: str, seconds: float | None = None) -> None:
|
|
226
|
+
"""A token asked for the `auth: exchange` API `api`; `seconds` when one was sent."""
|
|
227
|
+
if outcome not in TOKEN_EXCHANGE_OUTCOMES:
|
|
228
|
+
return
|
|
229
|
+
TOKEN_EXCHANGES.labels(api, outcome).inc()
|
|
230
|
+
if seconds is not None:
|
|
231
|
+
TOKEN_EXCHANGE_LATENCY.labels(api).observe(seconds)
|