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,340 @@
|
|
|
1
|
+
# Copyright 2026 Google LLC
|
|
2
|
+
# Modifications Copyright 2026 graph-agents-cli contributors
|
|
3
|
+
#
|
|
4
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
5
|
+
# you may not use this file except in compliance with the License.
|
|
6
|
+
# You may obtain a copy of the License at
|
|
7
|
+
#
|
|
8
|
+
# https://www.apache.org/licenses/LICENSE-2.0
|
|
9
|
+
#
|
|
10
|
+
# Unless required by applicable law or agreed to in writing, software
|
|
11
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
12
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
13
|
+
# See the License for the specific language governing permissions and
|
|
14
|
+
# limitations under the License.
|
|
15
|
+
|
|
16
|
+
"""End-to-end test of the server this project runs, in-process.
|
|
17
|
+
|
|
18
|
+
The app is started through its lifespan with `MODEL_PROVIDER=fake` and
|
|
19
|
+
`CHECKPOINTER=memory`, so no model key, database or network is needed, and
|
|
20
|
+
driven through `httpx.AsyncClient` over `ASGITransport`. Tool events are
|
|
21
|
+
exercised with the test-only `probe` tool (`use_test_tools`, see
|
|
22
|
+
`tests/conftest.py`), so these tests hold whatever tools the project has.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
import os
|
|
28
|
+
import uuid
|
|
29
|
+
from collections.abc import AsyncIterator
|
|
30
|
+
|
|
31
|
+
# The environment must be in place before the app (and the graph) is imported.
|
|
32
|
+
os.environ.update(
|
|
33
|
+
{
|
|
34
|
+
"MODEL_PROVIDER": "fake",
|
|
35
|
+
"MODEL_NAME": "fake",
|
|
36
|
+
"CHECKPOINTER": "memory",
|
|
37
|
+
"AUTH_POLICY": "shared-bearer",
|
|
38
|
+
"API_KEY": "test-key",
|
|
39
|
+
"APP_ENV": "dev",
|
|
40
|
+
"TRACING_ENABLED": "false",
|
|
41
|
+
"RUNTIME": "fastapi",
|
|
42
|
+
"APP_URL": "http://testserver",
|
|
43
|
+
}
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
import httpx
|
|
47
|
+
import pytest
|
|
48
|
+
from langchain_core.tools import tool
|
|
49
|
+
|
|
50
|
+
from {{cookiecutter.agent_directory}} import agent as agent_module
|
|
51
|
+
from {{cookiecutter.agent_directory}}.app_utils.auth import Principal, reset_policy_cache
|
|
52
|
+
from {{cookiecutter.agent_directory}}.app_utils.chat import RUNTIME
|
|
53
|
+
from {{cookiecutter.agent_directory}}.fast_api_app import app
|
|
54
|
+
|
|
55
|
+
AUTH = {"Authorization": "Bearer test-key"}
|
|
56
|
+
A2A_PATH = "/a2a/{{cookiecutter.agent_directory}}"
|
|
57
|
+
PROBE_PROMPT = "Run the probe for San Francisco"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@tool
|
|
61
|
+
def probe(query: str) -> str:
|
|
62
|
+
"""Test-only tool: reports what it was asked about."""
|
|
63
|
+
return f"probe reading for {query}: 42"
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@pytest.fixture
|
|
67
|
+
async def client() -> AsyncIterator[httpx.AsyncClient]:
|
|
68
|
+
async with app.router.lifespan_context(app):
|
|
69
|
+
transport = httpx.ASGITransport(app=app)
|
|
70
|
+
async with httpx.AsyncClient(
|
|
71
|
+
transport=transport, base_url="http://testserver", timeout=30
|
|
72
|
+
) as c:
|
|
73
|
+
yield c
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
async def _chat(
|
|
77
|
+
client: httpx.AsyncClient, message: str, thread_id: str | None = None
|
|
78
|
+
) -> list[tuple[str, dict]]:
|
|
79
|
+
body: dict = {"message": message}
|
|
80
|
+
if thread_id:
|
|
81
|
+
body["thread_id"] = thread_id
|
|
82
|
+
events: list[tuple[str, dict]] = []
|
|
83
|
+
async with client.stream(
|
|
84
|
+
"POST", "/chat", json=body, headers={**AUTH, "Accept": "text/event-stream"}
|
|
85
|
+
) as r:
|
|
86
|
+
assert r.status_code == 200, await r.aread()
|
|
87
|
+
assert r.headers["content-type"].startswith("text/event-stream")
|
|
88
|
+
event = None
|
|
89
|
+
async for line in r.aiter_lines():
|
|
90
|
+
if line.startswith("event:"):
|
|
91
|
+
event = line[6:].strip()
|
|
92
|
+
elif line.startswith("data:") and event:
|
|
93
|
+
import json
|
|
94
|
+
|
|
95
|
+
events.append((event, json.loads(line[5:].strip())))
|
|
96
|
+
event = None
|
|
97
|
+
return events
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
async def test_health(client: httpx.AsyncClient) -> None:
|
|
101
|
+
r = await client.get("/health")
|
|
102
|
+
assert r.status_code == 200
|
|
103
|
+
assert r.json() == {"status": "ok", "runtime": "fastapi", "checkpointer": "memory"}
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
async def test_chat_requires_the_bearer_key(client: httpx.AsyncClient) -> None:
|
|
107
|
+
r = await client.post("/chat", json={"message": "hi"})
|
|
108
|
+
assert r.status_code == 401
|
|
109
|
+
r = await client.post("/chat", json={"message": "hi"}, headers={"Authorization": "Bearer nope"})
|
|
110
|
+
assert r.status_code == 401
|
|
111
|
+
r = await client.get("/threads/x/messages")
|
|
112
|
+
assert r.status_code == 401
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
async def test_chat_sse_event_sequence_with_a_tool_call(
|
|
116
|
+
client: httpx.AsyncClient, use_test_tools
|
|
117
|
+
) -> None:
|
|
118
|
+
use_test_tools(probe)
|
|
119
|
+
events = await _chat(client, PROBE_PROMPT)
|
|
120
|
+
names = [e for e, _ in events]
|
|
121
|
+
assert names[0] == "message.start"
|
|
122
|
+
assert names[-1] == "message.end"
|
|
123
|
+
assert "tool.call" in names and "tool.result" in names
|
|
124
|
+
assert names.index("tool.call") < names.index("tool.result") < names.index("message.delta")
|
|
125
|
+
start, end = events[0][1], events[-1][1]
|
|
126
|
+
assert start["thread_id"] and start["run_id"] == end["run_id"]
|
|
127
|
+
call = next(d for e, d in events if e == "tool.call")
|
|
128
|
+
assert call["name"] == "probe" and call["args"] == {"query": "San Francisco"}
|
|
129
|
+
result = next(d for e, d in events if e == "tool.result")
|
|
130
|
+
assert result["id"] == call["id"] and result["is_error"] is False
|
|
131
|
+
assert result["result"] == "probe reading for San Francisco: 42"
|
|
132
|
+
text = "".join(d["text"] for e, d in events if e == "message.delta")
|
|
133
|
+
assert "probe reading for San Francisco: 42" in text
|
|
134
|
+
assert end["status"] == "ok" and end["usage"]["output_tokens"] > 0 and end["latency_ms"] >= 0
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def test_the_project_tools_do_not_matter_to_a_plain_chat(
|
|
138
|
+
client: httpx.AsyncClient, use_test_tools
|
|
139
|
+
) -> None:
|
|
140
|
+
"""With no tool bound at all the agent still answers (tools are the project's to change)."""
|
|
141
|
+
use_test_tools()
|
|
142
|
+
events = await _chat(client, PROBE_PROMPT)
|
|
143
|
+
names = [e for e, _ in events]
|
|
144
|
+
assert names[0] == "message.start" and names[-1] == "message.end"
|
|
145
|
+
assert "tool.call" not in names
|
|
146
|
+
text = "".join(d["text"] for e, d in events if e == "message.delta")
|
|
147
|
+
assert PROBE_PROMPT in text
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
async def test_thread_continuity_and_messages_endpoint(client: httpx.AsyncClient) -> None:
|
|
151
|
+
thread_id = str(uuid.uuid4())
|
|
152
|
+
first = await _chat(client, "hello", thread_id)
|
|
153
|
+
assert first[0][1]["thread_id"] == thread_id
|
|
154
|
+
second = await _chat(client, "thanks", thread_id)
|
|
155
|
+
assert second[0][1]["thread_id"] == thread_id
|
|
156
|
+
r = await client.get(f"/threads/{thread_id}/messages", headers=AUTH)
|
|
157
|
+
assert r.status_code == 200
|
|
158
|
+
messages = r.json()
|
|
159
|
+
assert [m["role"] for m in messages] == ["user", "assistant", "user", "assistant"]
|
|
160
|
+
assert messages[0]["content"] == "hello" and "Hello" in messages[1]["content"]
|
|
161
|
+
r = await client.get(f"/threads/{uuid.uuid4()}/messages", headers=AUTH)
|
|
162
|
+
assert r.status_code == 404
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
async def test_playground_only_in_dev(
|
|
166
|
+
client: httpx.AsyncClient, monkeypatch: pytest.MonkeyPatch
|
|
167
|
+
) -> None:
|
|
168
|
+
r = await client.get("/playground")
|
|
169
|
+
assert r.status_code == 200 and "text/html" in r.headers["content-type"] and "/chat" in r.text
|
|
170
|
+
for other in ("prod", "DEV", " dev "): # dev relaxes checks: only the exact value counts
|
|
171
|
+
monkeypatch.setenv("APP_ENV", other)
|
|
172
|
+
r = await client.get("/playground")
|
|
173
|
+
assert r.status_code == 404, other
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
async def test_openapi_and_docs_exist_only_in_dev(client: httpx.AsyncClient) -> None:
|
|
177
|
+
# This app was built under APP_ENV=dev: the schema and Swagger UI are on.
|
|
178
|
+
assert (await client.get("/openapi.json")).status_code == 200
|
|
179
|
+
assert (await client.get("/docs")).status_code == 200
|
|
180
|
+
assert (await client.get("/redoc")).status_code == 404
|
|
181
|
+
# Built with APP_ENV unset (the deployed default), they are gone.
|
|
182
|
+
import importlib
|
|
183
|
+
import os
|
|
184
|
+
|
|
185
|
+
from {{cookiecutter.agent_directory}} import fast_api_app as module
|
|
186
|
+
|
|
187
|
+
saved = os.environ.pop("APP_ENV", None)
|
|
188
|
+
try:
|
|
189
|
+
prod_app = importlib.reload(module).app
|
|
190
|
+
finally:
|
|
191
|
+
if saved is not None:
|
|
192
|
+
os.environ["APP_ENV"] = saved
|
|
193
|
+
importlib.reload(module)
|
|
194
|
+
async with httpx.AsyncClient(
|
|
195
|
+
transport=httpx.ASGITransport(app=prod_app), base_url="http://testserver"
|
|
196
|
+
) as prod:
|
|
197
|
+
assert (await prod.get("/openapi.json")).status_code == 404
|
|
198
|
+
assert (await prod.get("/docs")).status_code == 404
|
|
199
|
+
assert prod_app.openapi()["paths"] # LangGraph Server still builds its spec from it
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
async def test_chat_error_event_replaces_message_end_and_records_the_run(
|
|
203
|
+
client: httpx.AsyncClient, monkeypatch: pytest.MonkeyPatch
|
|
204
|
+
) -> None:
|
|
205
|
+
async def failing_astream(*args: object, **kwargs: object): # type: ignore[no-untyped-def]
|
|
206
|
+
raise RuntimeError("model down")
|
|
207
|
+
yield # pragma: no cover - makes this an async generator
|
|
208
|
+
|
|
209
|
+
# `_local_events` imports `graph` at call time, so patching the instance is enough.
|
|
210
|
+
monkeypatch.setattr(agent_module.graph, "astream", failing_astream)
|
|
211
|
+
events = await _chat(client, "hello")
|
|
212
|
+
names = [e for e, _ in events]
|
|
213
|
+
assert names == ["message.start", "error"], names
|
|
214
|
+
error = events[1][1]
|
|
215
|
+
run_id = events[0][1]["run_id"]
|
|
216
|
+
# A generic message and an id naming the logged detail; the detail itself
|
|
217
|
+
# only under APP_ENV=dev (this app runs in dev).
|
|
218
|
+
assert error["code"] == "run_failed" and error["run_id"] == run_id
|
|
219
|
+
assert error["message"] == f"The run failed. Reference: {error['error_id']}."
|
|
220
|
+
assert error["detail"] == "RuntimeError: model down"
|
|
221
|
+
monkeypatch.setenv("APP_ENV", "prod")
|
|
222
|
+
error = (await _chat(client, "hello"))[1][1]
|
|
223
|
+
assert "detail" not in error and "model down" not in str(error)
|
|
224
|
+
assert RUNTIME.runs is not None
|
|
225
|
+
record = await RUNTIME.runs.get(run_id)
|
|
226
|
+
assert record is not None
|
|
227
|
+
assert record.status == "error" and record.error_type == "RuntimeError"
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
@pytest.mark.parametrize(
|
|
231
|
+
("policy", "marker"),
|
|
232
|
+
[("custom", "AUTH_POLICY=custom"), ("jwt", "AUTH_POLICY=jwt"), ("product-session", "custom")],
|
|
233
|
+
)
|
|
234
|
+
async def test_stub_policies_answer_503_on_every_surface(
|
|
235
|
+
client: httpx.AsyncClient, monkeypatch: pytest.MonkeyPatch, policy: str, marker: str
|
|
236
|
+
) -> None:
|
|
237
|
+
"""`custom` (and its retired name) and the `jwt` placeholder fail closed everywhere."""
|
|
238
|
+
monkeypatch.setenv("AUTH_POLICY", policy)
|
|
239
|
+
reset_policy_cache()
|
|
240
|
+
try:
|
|
241
|
+
assert (await client.get("/health")).status_code == 200
|
|
242
|
+
for method, path, kwargs in (
|
|
243
|
+
("POST", "/chat", {"json": {"message": "hi"}, "headers": AUTH}),
|
|
244
|
+
("POST", "/chat", {"json": {"message": "hi"}, "headers": {"Cookie": "sid=1"}}),
|
|
245
|
+
("GET", "/threads/x/messages", {"headers": AUTH}),
|
|
246
|
+
("GET", f"{A2A_PATH}/.well-known/agent-card.json", {"headers": AUTH}),
|
|
247
|
+
("POST", A2A_PATH, {"json": {"jsonrpc": "2.0", "id": "1", "method": "SendMessage"}}),
|
|
248
|
+
):
|
|
249
|
+
r = await client.request(method, path, **kwargs)
|
|
250
|
+
assert r.status_code == 503, (method, path, r.text)
|
|
251
|
+
assert marker in r.json()["detail"]
|
|
252
|
+
finally:
|
|
253
|
+
reset_policy_cache()
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
async def test_thread_ownership_and_tool_args_redaction(
|
|
257
|
+
client: httpx.AsyncClient, monkeypatch: pytest.MonkeyPatch, use_test_tools
|
|
258
|
+
) -> None:
|
|
259
|
+
"""Non-owners are refused; a read-across role reads without tool args under metadata capture."""
|
|
260
|
+
use_test_tools(probe)
|
|
261
|
+
thread_id = str(uuid.uuid4())
|
|
262
|
+
await _chat(client, PROBE_PROMPT, thread_id)
|
|
263
|
+
owner = Principal(id="shared", roles=["shared"])
|
|
264
|
+
messages = await RUNTIME.messages(owner, thread_id)
|
|
265
|
+
assistant = next(m for m in messages if m.get("tool_calls"))
|
|
266
|
+
assert assistant["tool_calls"][0]["args"] == {"query": "San Francisco"}
|
|
267
|
+
|
|
268
|
+
from fastapi import HTTPException
|
|
269
|
+
|
|
270
|
+
with pytest.raises(HTTPException) as exc:
|
|
271
|
+
await RUNTIME.messages(Principal(id="stranger"), thread_id)
|
|
272
|
+
assert exc.value.status_code == 403
|
|
273
|
+
assert RUNTIME.threads is not None
|
|
274
|
+
with pytest.raises(HTTPException) as exc:
|
|
275
|
+
await RUNTIME.threads.ensure(thread_id, Principal(id="stranger"))
|
|
276
|
+
assert exc.value.status_code == 403
|
|
277
|
+
|
|
278
|
+
monkeypatch.setenv("AUTH_READ_ACROSS_ROLES", "auditor")
|
|
279
|
+
auditor = Principal(id="auditor", roles=["auditor"])
|
|
280
|
+
messages = await RUNTIME.messages(auditor, thread_id)
|
|
281
|
+
assistant = next(m for m in messages if m.get("tool_calls"))
|
|
282
|
+
assert assistant["tool_calls"][0]["name"] == "probe"
|
|
283
|
+
assert "args" not in assistant["tool_calls"][0]
|
|
284
|
+
monkeypatch.setenv("TRACE_CAPTURE", "full")
|
|
285
|
+
messages = await RUNTIME.messages(auditor, thread_id)
|
|
286
|
+
assert next(m for m in messages if m.get("tool_calls"))["tool_calls"][0]["args"] == {
|
|
287
|
+
"query": "San Francisco"
|
|
288
|
+
}
|
|
289
|
+
# Reading across is not writing: the auditor cannot continue the thread.
|
|
290
|
+
with pytest.raises(HTTPException) as exc:
|
|
291
|
+
await RUNTIME.threads.ensure(thread_id, auditor)
|
|
292
|
+
assert exc.value.status_code == 403
|
|
293
|
+
|
|
294
|
+
|
|
295
|
+
async def test_a2a_card_and_message(client: httpx.AsyncClient) -> None:
|
|
296
|
+
r = await client.get(f"{A2A_PATH}/.well-known/agent-card.json")
|
|
297
|
+
assert r.status_code == 401
|
|
298
|
+
r = await client.get(f"{A2A_PATH}/.well-known/agent-card.json", headers=AUTH)
|
|
299
|
+
assert r.status_code == 200
|
|
300
|
+
card = r.json()
|
|
301
|
+
assert card["name"] == "{{cookiecutter.agent_directory}}"
|
|
302
|
+
assert "bearer" in card["securitySchemes"]
|
|
303
|
+
assert card["supportedInterfaces"][0]["url"] == f"http://testserver{A2A_PATH}"
|
|
304
|
+
|
|
305
|
+
r = await client.post(A2A_PATH, json={"jsonrpc": "2.0", "id": "1", "method": "SendMessage"})
|
|
306
|
+
assert r.status_code == 401
|
|
307
|
+
|
|
308
|
+
from a2a.client import ClientConfig, create_client
|
|
309
|
+
from a2a.types import Message, Part, Role, SendMessageRequest, TaskState
|
|
310
|
+
|
|
311
|
+
transport = httpx.ASGITransport(app=app)
|
|
312
|
+
async with httpx.AsyncClient(
|
|
313
|
+
transport=transport, base_url="http://testserver", headers=AUTH, timeout=30
|
|
314
|
+
) as http:
|
|
315
|
+
a2a_client = await create_client(
|
|
316
|
+
f"http://testserver{A2A_PATH}", ClientConfig(streaming=True, httpx_client=http)
|
|
317
|
+
)
|
|
318
|
+
message = Message(
|
|
319
|
+
message_id=f"msg-{uuid.uuid4()}", role=Role.ROLE_USER, parts=[Part(text="Hi!")]
|
|
320
|
+
)
|
|
321
|
+
responses = [
|
|
322
|
+
chunk async for chunk in a2a_client.send_message(SendMessageRequest(message=message))
|
|
323
|
+
]
|
|
324
|
+
assert responses, "No responses received from the A2A stream"
|
|
325
|
+
|
|
326
|
+
def _completed(chunk: object) -> bool:
|
|
327
|
+
if chunk.HasField("status_update"):
|
|
328
|
+
return chunk.status_update.status.state == TaskState.TASK_STATE_COMPLETED
|
|
329
|
+
if chunk.HasField("task"):
|
|
330
|
+
return chunk.task.status.state == TaskState.TASK_STATE_COMPLETED
|
|
331
|
+
return False
|
|
332
|
+
|
|
333
|
+
text = "".join(
|
|
334
|
+
part.text
|
|
335
|
+
for chunk in responses
|
|
336
|
+
if chunk.HasField("artifact_update")
|
|
337
|
+
for part in chunk.artifact_update.artifact.parts
|
|
338
|
+
)
|
|
339
|
+
assert any(_completed(chunk) for chunk in responses), responses
|
|
340
|
+
assert "Hello" in text, responses
|