agent-api-server 2.1.14__tar.gz → 2.1.16__tar.gz
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.
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/PKG-INFO +23 -5
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/README.md +21 -3
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/agent_registration.py +9 -4
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/app.py +8 -4
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/cache/redis_cache.py +9 -4
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/config_center/config_center.py +37 -4
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/configs/config.py +3 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/redis_listener.py +2 -1
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/memory/postgres.py +5 -3
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/service_hub/service_hub.py +16 -8
- agent_api_server-2.1.16/agent_api_server/shared/redis_url.py +22 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/client.py +50 -25
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sso_service.py +21 -9
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/startup.py +10 -6
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/pyproject.toml +1 -1
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/agent_card_builder.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/call_a2a_client.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/langgraph_executor.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/redis_task_store.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/server.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/Makefile +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/agent.go +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/agent_test.go +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/go.mod +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/go.sum +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/main.go +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/api.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/config.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/graph.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/schema.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/api/v1/thread.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/cache/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/callback_handler.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/client/css/styles.css +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/client/favicon.ico +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/client/index.html +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/client/js/app.js +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/client/js/index.umd.js +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/configs/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/dynamic_llm/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/dynamic_llm/dynamic_llm.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/base.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/nats_listener.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/log/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/log/formatters.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/log/logging.json +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/mcp_convert/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/mcp_convert/mcp_convert.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/mcp_server.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/memory/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/agent_registry.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/modes/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/modes/chain.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/modes/discussion.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/orchestrator.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/types.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/register/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/register/register.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/service.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/service_hub/service_hub_test.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/services/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/services/config_builder.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/services/stream_service.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/ase.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/base_model.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/common.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/context_manager.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/decode_token.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/detect_message.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/exceptions.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/get_model_info.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/graph_loader.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/loop_agent.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/markdown_fixer.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/mcp_auth.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/message.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/model_config.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/model_manager_client.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/request_context.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/util_func.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/__init__.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/credential.py +0 -0
- {agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/encoding.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
2
|
Name: agent-api-server
|
|
3
|
-
Version: 2.1.
|
|
3
|
+
Version: 2.1.16
|
|
4
4
|
Summary: A Langgraph agent API server that implements Langgraph agent's web capabilities and can interact with chatbot
|
|
5
5
|
Author: zijie.zhang@advantech.com.cn
|
|
6
6
|
Requires-Python: >=3.10,<3.13
|
|
@@ -613,13 +613,13 @@ ACP 采用**代理模式(Proxy Mode)**,CLI 进程作为编辑器与 FastAP
|
|
|
613
613
|
|
|
614
614
|
```bash
|
|
615
615
|
# 从 PyPI 安装
|
|
616
|
-
pip install agent-api-server==2.1.
|
|
616
|
+
pip install agent-api-server==2.1.16
|
|
617
617
|
|
|
618
618
|
# 或从本地 wheel 安装
|
|
619
|
-
pip install dist/agent_api_server-2.1.
|
|
619
|
+
pip install dist/agent_api_server-2.1.16-py3-none-any.whl
|
|
620
620
|
```
|
|
621
621
|
|
|
622
|
-
`2.1.
|
|
622
|
+
`2.1.16` 使用本仓库的 LangGraph 0.6 依赖,并包含 WisePortal 组织适配、Redis `mode=standalone` 地址兼容,以及配置中心、SSO 和 ServiceHub 启动修复。PyPI 上另有使用 LangGraph 1.x 的 `2.2.x` 版本,安装本版本时请指定版本号。
|
|
623
623
|
|
|
624
624
|
ACP 命令使用仓库中的 Go 实现,需要在源码目录单独构建,并将生成的程序放入 `PATH`:
|
|
625
625
|
|
|
@@ -948,6 +948,8 @@ pip install agent-api-server[a2a]
|
|
|
948
948
|
SSO_URL=http://sso/v4.0
|
|
949
949
|
CLIENT_ID=your_client_id
|
|
950
950
|
CLIENT_SECRET=your_client_secret
|
|
951
|
+
# 自动查询/注册 SSO 客户端时使用;不同应用应使用不同名称。
|
|
952
|
+
# SSO_SERVICE_NAME=your-agent-service
|
|
951
953
|
|
|
952
954
|
# 数据库
|
|
953
955
|
POSTGRES_URL=postgresql://user:password@host:port/dbname
|
|
@@ -971,6 +973,13 @@ AGENT_AUTO_REGISTRATION=false
|
|
|
971
973
|
ENABLE_A2A=false
|
|
972
974
|
```
|
|
973
975
|
|
|
976
|
+
`SSO_URL` 支持服务根地址或带 `/v4.0` 的地址,也可带末尾斜杠。
|
|
977
|
+
`CLIENT_ID` 和 `CLIENT_SECRET` 同时配置时直接使用;两项均为空时自动查询或注册
|
|
978
|
+
SSO 客户端,只配置其中一项会报错。`SSO_SERVICE_NAME` 未设置时兼容旧名称
|
|
979
|
+
`KB-Insight`,其他应用应显式设置自己的名称。
|
|
980
|
+
提供 `ENSAAS_SERVICES` 时,ServiceHub 的数据库、Redis 和可选存储凭证会在
|
|
981
|
+
创建连接对象前加载,用户名及密码中的特殊字符会进行 URL 编码。
|
|
982
|
+
|
|
974
983
|
### WisePortal 配置
|
|
975
984
|
|
|
976
985
|
服务启动时会从 `${CONFIG_CENTER_ADDRESS}/v1/public/configs`
|
|
@@ -980,6 +989,15 @@ ENABLE_A2A=false
|
|
|
980
989
|
配置读取失败时记录警告并保留当前开关值,初始默认值为 `false`。
|
|
981
990
|
REST 与独立 MCP 服务都会在启动时读取配置;修改配置后需重启服务。
|
|
982
991
|
|
|
992
|
+
同一公共接口返回的 `datacenter`、`cluster`、`workspace`、`namespace` 会在初始化
|
|
993
|
+
SSO 前写入同名环境变量,供 SSO 客户端查询、注册和客户端 Token 范围校验使用,
|
|
994
|
+
与 WisePortal 开关是否开启无关。接口返回的非空字符串优先;字段缺失或为空、
|
|
995
|
+
或配置读取失败时保留已有环境变量。启动日志会输出实际使用的 SSO 范围,并提示仍缺失的字段。
|
|
996
|
+
|
|
997
|
+
设置 `ENABLE_CONFIG_CENTER=true` 后,还会从 `/v1/services` 读取 SSO 和 Model Manager
|
|
998
|
+
的 API 地址,同步到全局配置与同名环境变量。此接口读取失败或某个地址为空时,
|
|
999
|
+
保留原有地址;关闭该开关仍会读取上述公共配置。
|
|
1000
|
+
|
|
983
1001
|
- 关闭时,REST 继续读取 Cookie `TSTenant`,MCP 继续读取 Header `TSTenant`。
|
|
984
1002
|
- 开启时,优先读取 Header `Org-Str-Id`,其次 Header `orgStrId`、Cookie `orgStrId`,
|
|
985
1003
|
将该值作为 `TSTenant`。缺少组织字段时不会回退到旧租户字段。
|
|
@@ -572,13 +572,13 @@ ACP 采用**代理模式(Proxy Mode)**,CLI 进程作为编辑器与 FastAP
|
|
|
572
572
|
|
|
573
573
|
```bash
|
|
574
574
|
# 从 PyPI 安装
|
|
575
|
-
pip install agent-api-server==2.1.
|
|
575
|
+
pip install agent-api-server==2.1.16
|
|
576
576
|
|
|
577
577
|
# 或从本地 wheel 安装
|
|
578
|
-
pip install dist/agent_api_server-2.1.
|
|
578
|
+
pip install dist/agent_api_server-2.1.16-py3-none-any.whl
|
|
579
579
|
```
|
|
580
580
|
|
|
581
|
-
`2.1.
|
|
581
|
+
`2.1.16` 使用本仓库的 LangGraph 0.6 依赖,并包含 WisePortal 组织适配、Redis `mode=standalone` 地址兼容,以及配置中心、SSO 和 ServiceHub 启动修复。PyPI 上另有使用 LangGraph 1.x 的 `2.2.x` 版本,安装本版本时请指定版本号。
|
|
582
582
|
|
|
583
583
|
ACP 命令使用仓库中的 Go 实现,需要在源码目录单独构建,并将生成的程序放入 `PATH`:
|
|
584
584
|
|
|
@@ -907,6 +907,8 @@ pip install agent-api-server[a2a]
|
|
|
907
907
|
SSO_URL=http://sso/v4.0
|
|
908
908
|
CLIENT_ID=your_client_id
|
|
909
909
|
CLIENT_SECRET=your_client_secret
|
|
910
|
+
# 自动查询/注册 SSO 客户端时使用;不同应用应使用不同名称。
|
|
911
|
+
# SSO_SERVICE_NAME=your-agent-service
|
|
910
912
|
|
|
911
913
|
# 数据库
|
|
912
914
|
POSTGRES_URL=postgresql://user:password@host:port/dbname
|
|
@@ -930,6 +932,13 @@ AGENT_AUTO_REGISTRATION=false
|
|
|
930
932
|
ENABLE_A2A=false
|
|
931
933
|
```
|
|
932
934
|
|
|
935
|
+
`SSO_URL` 支持服务根地址或带 `/v4.0` 的地址,也可带末尾斜杠。
|
|
936
|
+
`CLIENT_ID` 和 `CLIENT_SECRET` 同时配置时直接使用;两项均为空时自动查询或注册
|
|
937
|
+
SSO 客户端,只配置其中一项会报错。`SSO_SERVICE_NAME` 未设置时兼容旧名称
|
|
938
|
+
`KB-Insight`,其他应用应显式设置自己的名称。
|
|
939
|
+
提供 `ENSAAS_SERVICES` 时,ServiceHub 的数据库、Redis 和可选存储凭证会在
|
|
940
|
+
创建连接对象前加载,用户名及密码中的特殊字符会进行 URL 编码。
|
|
941
|
+
|
|
933
942
|
### WisePortal 配置
|
|
934
943
|
|
|
935
944
|
服务启动时会从 `${CONFIG_CENTER_ADDRESS}/v1/public/configs`
|
|
@@ -939,6 +948,15 @@ ENABLE_A2A=false
|
|
|
939
948
|
配置读取失败时记录警告并保留当前开关值,初始默认值为 `false`。
|
|
940
949
|
REST 与独立 MCP 服务都会在启动时读取配置;修改配置后需重启服务。
|
|
941
950
|
|
|
951
|
+
同一公共接口返回的 `datacenter`、`cluster`、`workspace`、`namespace` 会在初始化
|
|
952
|
+
SSO 前写入同名环境变量,供 SSO 客户端查询、注册和客户端 Token 范围校验使用,
|
|
953
|
+
与 WisePortal 开关是否开启无关。接口返回的非空字符串优先;字段缺失或为空、
|
|
954
|
+
或配置读取失败时保留已有环境变量。启动日志会输出实际使用的 SSO 范围,并提示仍缺失的字段。
|
|
955
|
+
|
|
956
|
+
设置 `ENABLE_CONFIG_CENTER=true` 后,还会从 `/v1/services` 读取 SSO 和 Model Manager
|
|
957
|
+
的 API 地址,同步到全局配置与同名环境变量。此接口读取失败或某个地址为空时,
|
|
958
|
+
保留原有地址;关闭该开关仍会读取上述公共配置。
|
|
959
|
+
|
|
942
960
|
- 关闭时,REST 继续读取 Cookie `TSTenant`,MCP 继续读取 Header `TSTenant`。
|
|
943
961
|
- 开启时,优先读取 Header `Org-Str-Id`,其次 Header `orgStrId`、Cookie `orgStrId`,
|
|
944
962
|
将该值作为 `TSTenant`。缺少组织字段时不会回退到旧租户字段。
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Agent registration and listener management service."""
|
|
2
2
|
import os
|
|
3
|
+
import asyncio
|
|
3
4
|
import threading
|
|
4
5
|
from typing import List, Dict, Any
|
|
5
6
|
|
|
@@ -29,25 +30,29 @@ class AgentRegistrationService:
|
|
|
29
30
|
SSOInitializationError: If SSO initialization fails
|
|
30
31
|
"""
|
|
31
32
|
try:
|
|
32
|
-
sso_service =
|
|
33
|
+
sso_service = await asyncio.to_thread(
|
|
34
|
+
SSOService,
|
|
33
35
|
sso_config=SSOConfig(
|
|
34
36
|
sso_address=global_config.SSO_URL,
|
|
35
37
|
client_id=global_config.CLIENT_ID,
|
|
36
38
|
client_secret=global_config.CLIENT_SECRET,
|
|
39
|
+
service_name=global_config.SSO_SERVICE_NAME,
|
|
37
40
|
)
|
|
38
41
|
)
|
|
39
42
|
|
|
40
43
|
client_id, client_secret = sso_service.get_client_id_and_secret()
|
|
41
44
|
logger.debug("SSO credentials obtained - Client ID: %s", client_id)
|
|
42
45
|
|
|
43
|
-
self._client_token =
|
|
44
|
-
client_id, client_secret
|
|
46
|
+
self._client_token = await asyncio.to_thread(
|
|
47
|
+
sso_service.generate_client_token, client_id, client_secret
|
|
45
48
|
)
|
|
46
49
|
|
|
50
|
+
global_config.CLIENT_ID = client_id
|
|
51
|
+
global_config.CLIENT_SECRET = client_secret
|
|
47
52
|
global_config.CLIENT_TOKEN = self._client_token
|
|
48
53
|
os.environ['CLIENT_TOKEN'] = self._client_token
|
|
49
54
|
|
|
50
|
-
logger.info(
|
|
55
|
+
logger.info("SSO initialization completed successfully")
|
|
51
56
|
return self._client_token
|
|
52
57
|
|
|
53
58
|
except Exception as e:
|
|
@@ -61,6 +61,7 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
|
61
61
|
"""Single source of truth for application lifecycle.
|
|
62
62
|
|
|
63
63
|
Initialization order:
|
|
64
|
+
0. Configuration and credentials
|
|
64
65
|
1. Infrastructure (Postgres + Redis)
|
|
65
66
|
2. Schema migration (advisory-lock protected, multi-worker safe)
|
|
66
67
|
3. SSO + Agent registration + Listeners (if not already done by init_fastapi_server)
|
|
@@ -70,11 +71,15 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
|
70
71
|
from agent_api_server.startup import ApplicationStartup
|
|
71
72
|
|
|
72
73
|
startup = ApplicationStartup()
|
|
73
|
-
checkpointer = AsyncPostgresCheckpointer.get_worker_instance()
|
|
74
|
-
cache = AsyncRedisThreadStorage.get_worker_instance()
|
|
75
74
|
|
|
76
75
|
try:
|
|
77
|
-
|
|
76
|
+
if not ApplicationStartup._initialized:
|
|
77
|
+
await initialize_wiseportal_config()
|
|
78
|
+
await startup._initialize_credentials()
|
|
79
|
+
|
|
80
|
+
# Constructors snapshot their connection URLs, so configuration must be ready first.
|
|
81
|
+
checkpointer = AsyncPostgresCheckpointer.get_worker_instance()
|
|
82
|
+
cache = AsyncRedisThreadStorage.get_worker_instance()
|
|
78
83
|
|
|
79
84
|
# 1. Infrastructure (always run, these are per-worker)
|
|
80
85
|
await checkpointer.initialize()
|
|
@@ -90,7 +95,6 @@ async def lifespan(app: FastAPI) -> AsyncIterator[None]:
|
|
|
90
95
|
# 3. Credentials, SSO, Agent registration, Listeners
|
|
91
96
|
# Only run if not already initialized by init_fastapi_server()
|
|
92
97
|
if not ApplicationStartup._initialized:
|
|
93
|
-
await startup._initialize_credentials()
|
|
94
98
|
await startup.registration_service.initialize_sso()
|
|
95
99
|
|
|
96
100
|
agents = await startup._load_agent_config()
|
|
@@ -9,6 +9,7 @@ from uuid import uuid4
|
|
|
9
9
|
from dataclasses import dataclass
|
|
10
10
|
import redis.asyncio as redis
|
|
11
11
|
from agent_api_server.configs import global_config
|
|
12
|
+
from agent_api_server.shared.redis_url import normalize_redis_url
|
|
12
13
|
from redis.exceptions import RedisError, WatchError
|
|
13
14
|
|
|
14
15
|
logger = logging.getLogger(__name__)
|
|
@@ -65,9 +66,13 @@ class AsyncRedisThreadStorage:
|
|
|
65
66
|
|
|
66
67
|
@staticmethod
|
|
67
68
|
def _get_redis_url() -> str:
|
|
68
|
-
"""
|
|
69
|
-
redis_url = global_config.REDIS_URL
|
|
70
|
-
|
|
69
|
+
"""Normalize the configured Redis URL and redact credentials in logs."""
|
|
70
|
+
redis_url = normalize_redis_url(global_config.REDIS_URL)
|
|
71
|
+
scheme = redis_url.partition("://")[0]
|
|
72
|
+
safe_redis_url = (
|
|
73
|
+
f"{scheme}://[REDACTED]@{redis_url.rsplit('@', 1)[1]}"
|
|
74
|
+
if '@' in redis_url else redis_url
|
|
75
|
+
)
|
|
71
76
|
logger.debug("Using Redis URL: %s", safe_redis_url)
|
|
72
77
|
return redis_url
|
|
73
78
|
|
|
@@ -382,4 +387,4 @@ class AsyncRedisThreadStorage:
|
|
|
382
387
|
return bool(exists) and status != b"stopped"
|
|
383
388
|
except RedisError as e:
|
|
384
389
|
logger.error(f"Failed to check stop flag: {str(e)}")
|
|
385
|
-
return False
|
|
390
|
+
return False
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/config_center/config_center.py
RENAMED
|
@@ -244,17 +244,50 @@ def parse_wiseportal_enabled(value) -> bool:
|
|
|
244
244
|
return value is True or (isinstance(value, str) and value.strip().lower() == "true")
|
|
245
245
|
|
|
246
246
|
|
|
247
|
-
async def
|
|
248
|
-
"""Load the portal mode during server startup."""
|
|
249
|
-
client = AsyncConfigCenterClient()
|
|
247
|
+
async def _initialize_public_config(client: AsyncConfigCenterClient) -> None:
|
|
250
248
|
try:
|
|
251
249
|
config = await client.public_config()
|
|
252
250
|
global_config.isWisePortal = config["isWisePortal"]
|
|
251
|
+
scope_fields = ("datacenter", "cluster", "workspace", "namespace")
|
|
252
|
+
for field in scope_fields:
|
|
253
|
+
value = config.get(field)
|
|
254
|
+
if isinstance(value, str) and value.strip():
|
|
255
|
+
# SSOClient and verify_client_token both read these environment keys.
|
|
256
|
+
os.environ[field] = value.strip()
|
|
257
|
+
|
|
253
258
|
logger.info("WisePortal mode: %s", global_config.isWisePortal)
|
|
259
|
+
logger.info("SSO scope: %s", {field: os.getenv(field) for field in scope_fields})
|
|
260
|
+
missing = [field for field in scope_fields if not os.getenv(field, "").strip()]
|
|
261
|
+
if missing:
|
|
262
|
+
logger.warning(
|
|
263
|
+
"Missing SSO scope fields: %s; configure them in /v1/public/configs "
|
|
264
|
+
"or environment variables",
|
|
265
|
+
", ".join(missing),
|
|
266
|
+
)
|
|
254
267
|
except (httpx.HTTPError, ValueError, TypeError, AttributeError) as exc:
|
|
255
268
|
logger.warning(
|
|
256
|
-
"Unable to load
|
|
269
|
+
"Unable to load public config; retaining existing SSO scope and isWisePortal=%s: %s",
|
|
257
270
|
global_config.isWisePortal, exc,
|
|
258
271
|
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
async def initialize_wiseportal_config() -> None:
|
|
275
|
+
"""Load public scope and optional service discovery before clients are created."""
|
|
276
|
+
client = AsyncConfigCenterClient()
|
|
277
|
+
try:
|
|
278
|
+
await _initialize_public_config(client)
|
|
279
|
+
if global_config.ENABLE_CONFIG_CENTER:
|
|
280
|
+
try:
|
|
281
|
+
services = await client.service_config()
|
|
282
|
+
for field in ("SSO_URL", "MODEL_MANAGER_SERVICE_URL"):
|
|
283
|
+
address = services.get(field)
|
|
284
|
+
if isinstance(address, str) and address.strip():
|
|
285
|
+
address = address.strip().rstrip("/")
|
|
286
|
+
setattr(global_config, field, address)
|
|
287
|
+
# Legacy model-manager paths still read the environment.
|
|
288
|
+
os.environ[field] = address
|
|
289
|
+
logger.info("ConfigCenter service addresses loaded")
|
|
290
|
+
except (httpx.HTTPError, ValueError, TypeError, AttributeError) as exc:
|
|
291
|
+
logger.warning("Unable to load service config; retaining existing addresses: %s", exc)
|
|
259
292
|
finally:
|
|
260
293
|
await client.close()
|
|
@@ -12,6 +12,9 @@ class SSOConfig(BaseSettings):
|
|
|
12
12
|
CLIENT_ID: str = Field(default="", description="The client-id of server")
|
|
13
13
|
CLIENT_SECRET: str = Field(default="", description="The client secret of server")
|
|
14
14
|
CLIENT_TOKEN: str = Field(default="", description="The client token of server")
|
|
15
|
+
SSO_SERVICE_NAME: str = Field(
|
|
16
|
+
default="", description="SSO client service name; defaults to KB-Insight for compatibility"
|
|
17
|
+
)
|
|
15
18
|
|
|
16
19
|
|
|
17
20
|
class CacheConfig(BaseSettings):
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/redis_listener.py
RENAMED
|
@@ -8,6 +8,7 @@ import redis
|
|
|
8
8
|
|
|
9
9
|
from agent_api_server.configs import global_config
|
|
10
10
|
from agent_api_server.listener.base import MessageListener
|
|
11
|
+
from agent_api_server.shared.redis_url import normalize_redis_url
|
|
11
12
|
|
|
12
13
|
import logging
|
|
13
14
|
|
|
@@ -48,7 +49,7 @@ class RedisListener(MessageListener):
|
|
|
48
49
|
"""Establish Redis connection and subscribe to channels."""
|
|
49
50
|
try:
|
|
50
51
|
self.redis_client = redis.Redis.from_url(
|
|
51
|
-
url=global_config.MODEL_MANAGER_REDIS_URL,
|
|
52
|
+
url=normalize_redis_url(global_config.MODEL_MANAGER_REDIS_URL),
|
|
52
53
|
socket_timeout=10,
|
|
53
54
|
socket_keepalive=True,
|
|
54
55
|
health_check_interval=15,
|
|
@@ -5,6 +5,7 @@ from typing import Optional, cast, Dict, Any
|
|
|
5
5
|
from psycopg_pool import AsyncConnectionPool
|
|
6
6
|
from psycopg.rows import dict_row
|
|
7
7
|
from psycopg import OperationalError, AsyncConnection
|
|
8
|
+
from psycopg.conninfo import conninfo_to_dict
|
|
8
9
|
from agent_api_server.configs import global_config
|
|
9
10
|
from tenacity import (
|
|
10
11
|
retry,
|
|
@@ -75,8 +76,9 @@ class AsyncPostgresCheckpointer:
|
|
|
75
76
|
def _get_conn_str() -> str:
|
|
76
77
|
"""获取安全的连接字符串(隐藏密码)"""
|
|
77
78
|
conn_str = global_config.POSTGRES_URL
|
|
78
|
-
|
|
79
|
-
logger.debug("Using connection
|
|
79
|
+
connection = conninfo_to_dict(conn_str)
|
|
80
|
+
logger.debug("Using PostgreSQL connection: host=%s port=%s dbname=%s",
|
|
81
|
+
connection.get("host"), connection.get("port"), connection.get("dbname"))
|
|
80
82
|
return conn_str
|
|
81
83
|
|
|
82
84
|
@classmethod
|
|
@@ -264,4 +266,4 @@ class AsyncPostgresCheckpointer:
|
|
|
264
266
|
|
|
265
267
|
@property
|
|
266
268
|
def is_initialized(self):
|
|
267
|
-
return self._is_initialized
|
|
269
|
+
return self._is_initialized
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/service_hub/service_hub.py
RENAMED
|
@@ -3,6 +3,7 @@ import os
|
|
|
3
3
|
import logging
|
|
4
4
|
from typing import Dict, List, Union
|
|
5
5
|
from dataclasses import dataclass, field
|
|
6
|
+
from urllib.parse import quote
|
|
6
7
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
@@ -85,31 +86,35 @@ class ServiceHubCredentials:
|
|
|
85
86
|
raise CredentialNotFoundError("PostgreSQL credentials not found")
|
|
86
87
|
|
|
87
88
|
cred = self.postgres[0]
|
|
88
|
-
return
|
|
89
|
+
return (
|
|
90
|
+
f"postgresql://{quote(cred.username, safe='')}:{quote(cred.password, safe='')}"
|
|
91
|
+
f"@{cred.internal_hosts}/{quote(cred.database, safe='')}"
|
|
92
|
+
)
|
|
89
93
|
|
|
90
94
|
def to_redis_dsn(self) -> str:
|
|
91
95
|
if not self.redis:
|
|
92
96
|
raise CredentialNotFoundError("Redis credentials not found")
|
|
93
97
|
|
|
94
98
|
cred = self.redis[0]
|
|
99
|
+
username = quote(cred.username, safe="")
|
|
100
|
+
password = quote(cred.password, safe="")
|
|
95
101
|
|
|
96
102
|
if not cred.password and not cred.username:
|
|
97
103
|
return f"redis://{cred.internal_hosts}:{cred.port}"
|
|
98
104
|
|
|
99
105
|
if cred.password and not cred.username:
|
|
100
|
-
return f"redis://:{
|
|
106
|
+
return f"redis://:{password}@{cred.internal_hosts}:{cred.port}"
|
|
101
107
|
|
|
102
108
|
if cred.username and not cred.password:
|
|
103
|
-
return f"redis://{
|
|
109
|
+
return f"redis://{username}@{cred.internal_hosts}:{cred.port}"
|
|
104
110
|
|
|
105
111
|
if cred.username and cred.password:
|
|
106
|
-
return f"redis://{
|
|
112
|
+
return f"redis://{username}:{password}@{cred.internal_hosts}:{cred.port}"
|
|
107
113
|
|
|
108
114
|
def get_blobstore_s3_config(self) -> Dict[str, str]:
|
|
109
115
|
"""Get Blobstore credentials as S3-compatible configuration dictionary"""
|
|
110
116
|
if not self.blobstore:
|
|
111
|
-
|
|
112
|
-
return {}
|
|
117
|
+
raise CredentialNotFoundError("Blobstore credentials not found")
|
|
113
118
|
|
|
114
119
|
cred = self.blobstore[0]
|
|
115
120
|
return {
|
|
@@ -182,7 +187,10 @@ class ServiceHubCredentialParser:
|
|
|
182
187
|
endpoint=data.get("credentials", {}).get("endpoint", ""),
|
|
183
188
|
external_hosts=data.get("credentials", {}).get("externalHosts", ""),
|
|
184
189
|
internal_hosts=data.get("credentials", {}).get("internalHosts", ""),
|
|
185
|
-
signature_version=
|
|
190
|
+
signature_version=(
|
|
191
|
+
data.get("credentials", {}).get("signatureVersion")
|
|
192
|
+
or data.get("credentials", {}).get("signature-version", "")
|
|
193
|
+
),
|
|
186
194
|
type=data.get("credentials", {}).get("type", ""),
|
|
187
195
|
instance_name=data.get("instance_name", ""),
|
|
188
196
|
label=data.get("label", ""),
|
|
@@ -230,4 +238,4 @@ class ServiceHubCredentialParser:
|
|
|
230
238
|
class ServiceHubCredentialLoader:
|
|
231
239
|
@staticmethod
|
|
232
240
|
def load_credentials() -> ServiceHubCredentials:
|
|
233
|
-
return ServiceHubCredentialParser.parse(os.getenv("ENSAAS_SERVICES"))
|
|
241
|
+
return ServiceHubCredentialParser.parse(os.getenv("ENSAAS_SERVICES"))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Adapt Redis URLs from configuration providers for redis-py clients."""
|
|
2
|
+
|
|
3
|
+
from urllib.parse import parse_qsl, urlencode, urlsplit, urlunsplit
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def normalize_redis_url(url: str) -> str:
|
|
7
|
+
"""Remove standalone topology metadata while retaining connection options.
|
|
8
|
+
|
|
9
|
+
redis-py forwards URL query parameters to the connection constructor, which
|
|
10
|
+
does not accept the config center's ``mode`` parameter. Other topologies
|
|
11
|
+
require dedicated clients and must not silently use a standalone client.
|
|
12
|
+
"""
|
|
13
|
+
parsed = urlsplit(url)
|
|
14
|
+
parameters = parse_qsl(parsed.query, keep_blank_values=True)
|
|
15
|
+
modes = [value for name, value in parameters if name == "mode"]
|
|
16
|
+
if not modes:
|
|
17
|
+
return url
|
|
18
|
+
if any(mode.lower() != "standalone" for mode in modes):
|
|
19
|
+
raise ValueError("Unsupported Redis mode; only mode=standalone is supported")
|
|
20
|
+
|
|
21
|
+
query = urlencode([(name, value) for name, value in parameters if name != "mode"])
|
|
22
|
+
return urlunsplit(parsed._replace(query=query))
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/client.py
RENAMED
|
@@ -11,6 +11,14 @@ logger = logging.getLogger(__name__)
|
|
|
11
11
|
|
|
12
12
|
CLIENT_CREDENTIALS = "client_credentials"
|
|
13
13
|
CURRENT_SERVICE_NAME = "KB-Insight"
|
|
14
|
+
SSO_REQUEST_TIMEOUT = 30.0
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def normalize_sso_address(address: str) -> str:
|
|
18
|
+
if not isinstance(address, str) or not address.strip():
|
|
19
|
+
raise ValueError("SSO service address is required")
|
|
20
|
+
address = address.strip().rstrip("/")
|
|
21
|
+
return address if address.endswith("/v4.0") else f"{address}/v4.0"
|
|
14
22
|
|
|
15
23
|
|
|
16
24
|
class ClientRequest:
|
|
@@ -117,32 +125,40 @@ class SsoToken:
|
|
|
117
125
|
|
|
118
126
|
class SSOClient:
|
|
119
127
|
def __init__(self, service_name: Optional[str] = None):
|
|
128
|
+
service_name = service_name or os.getenv("SSO_SERVICE_NAME") or CURRENT_SERVICE_NAME
|
|
120
129
|
self.client_info = ClientInfo(
|
|
121
130
|
datacenter=os.getenv("datacenter"),
|
|
122
131
|
cluster=os.getenv("cluster"),
|
|
123
132
|
workspace=os.getenv("workspace"),
|
|
124
133
|
namespace=os.getenv("namespace"),
|
|
125
134
|
app_id=os.getenv("appID") or os.getenv("appId"),
|
|
126
|
-
app_name=service_name
|
|
127
|
-
service_name=service_name
|
|
135
|
+
app_name=service_name,
|
|
136
|
+
service_name=service_name,
|
|
128
137
|
)
|
|
129
138
|
|
|
130
139
|
def query_sso_client(self, address: str, req: ClientRequest) -> ClientInfo:
|
|
131
|
-
path = f"{address}/clients/{quote(req.app_name)}"
|
|
132
|
-
|
|
140
|
+
path = f"{normalize_sso_address(address)}/clients/{quote(req.app_name, safe='')}"
|
|
141
|
+
params = {
|
|
142
|
+
"namespace": req.namespace,
|
|
143
|
+
"workspace": req.workspace,
|
|
144
|
+
"cluster": req.cluster,
|
|
145
|
+
"datacenter": req.datacenter,
|
|
146
|
+
"serviceName": req.service_name,
|
|
147
|
+
"appId": req.app_id,
|
|
148
|
+
}
|
|
133
149
|
|
|
134
150
|
credential = self.new_srp_token_credential(req.service_name)
|
|
135
151
|
headers = outbound_headers({credential["header_name"]: credential["header_value"]})
|
|
136
152
|
|
|
137
|
-
response = requests.get(path, headers=headers)
|
|
153
|
+
response = requests.get(path, params=params, headers=headers, timeout=SSO_REQUEST_TIMEOUT)
|
|
138
154
|
response.raise_for_status()
|
|
139
155
|
return ClientInfo.from_dict(response.json())
|
|
140
156
|
|
|
141
157
|
def register_client(self, address: str, req: ClientRequest) -> ClientInfo:
|
|
142
|
-
path = f"{address}/clients"
|
|
158
|
+
path = f"{normalize_sso_address(address)}/clients"
|
|
143
159
|
body = json.dumps(req.to_dict())
|
|
144
160
|
|
|
145
|
-
logger.info(
|
|
161
|
+
logger.info("Registering SSO client for service %s", req.service_name)
|
|
146
162
|
|
|
147
163
|
credential = self.new_srp_token_credential(req.service_name)
|
|
148
164
|
headers = {
|
|
@@ -150,12 +166,14 @@ class SSOClient:
|
|
|
150
166
|
"Content-Type": "application/json"
|
|
151
167
|
}
|
|
152
168
|
|
|
153
|
-
response = requests.post(
|
|
169
|
+
response = requests.post(
|
|
170
|
+
path, headers=outbound_headers(headers), data=body, timeout=SSO_REQUEST_TIMEOUT
|
|
171
|
+
)
|
|
154
172
|
response.raise_for_status()
|
|
155
173
|
return ClientInfo.from_dict(response.json())
|
|
156
174
|
|
|
157
175
|
def update_sso_client(self, address: str, client_id: str, req: ClientRequest) -> ClientInfo:
|
|
158
|
-
path = f"{address}/clients/{client_id}"
|
|
176
|
+
path = f"{normalize_sso_address(address)}/clients/{quote(client_id, safe='')}"
|
|
159
177
|
body = json.dumps(req.to_dict())
|
|
160
178
|
|
|
161
179
|
credential = self.new_srp_token_credential(req.service_name)
|
|
@@ -164,39 +182,46 @@ class SSOClient:
|
|
|
164
182
|
"Content-Type": "application/json"
|
|
165
183
|
}
|
|
166
184
|
|
|
167
|
-
response = requests.put(
|
|
185
|
+
response = requests.put(
|
|
186
|
+
path, headers=outbound_headers(headers), data=body, timeout=SSO_REQUEST_TIMEOUT
|
|
187
|
+
)
|
|
168
188
|
response.raise_for_status()
|
|
169
189
|
return ClientInfo.from_dict(response.json())
|
|
170
190
|
|
|
171
191
|
def query_sso_client_token(self, address: str) -> SsoToken:
|
|
172
|
-
path = f"{address}/oauth/token"
|
|
173
|
-
|
|
192
|
+
path = f"{normalize_sso_address(address)}/oauth/token"
|
|
193
|
+
params = {
|
|
194
|
+
"grant_type": CLIENT_CREDENTIALS,
|
|
195
|
+
"client_id": self.client_info.client_id,
|
|
196
|
+
"client_secret": self.client_info.client_secret,
|
|
197
|
+
"duration": "eternal",
|
|
198
|
+
}
|
|
174
199
|
|
|
175
|
-
response = requests.post(
|
|
200
|
+
response = requests.post(
|
|
201
|
+
path, params=params, headers=outbound_headers(), timeout=SSO_REQUEST_TIMEOUT
|
|
202
|
+
)
|
|
176
203
|
response.raise_for_status()
|
|
177
204
|
return SsoToken.from_dict(response.json())
|
|
178
205
|
|
|
179
206
|
def set_sso_client(self, address: str) -> None:
|
|
180
|
-
|
|
181
|
-
address += "/v4.0"
|
|
207
|
+
address = normalize_sso_address(address)
|
|
182
208
|
|
|
183
209
|
try:
|
|
184
210
|
rsp = self.query_sso_client(address, self.client_info)
|
|
185
211
|
except requests.exceptions.HTTPError as e:
|
|
186
|
-
if
|
|
212
|
+
if e.response is not None and e.response.status_code == 404:
|
|
187
213
|
rsp = self.register_client(address, self.client_info)
|
|
188
214
|
else:
|
|
189
|
-
raise
|
|
215
|
+
raise
|
|
190
216
|
|
|
191
217
|
if rsp is None:
|
|
192
218
|
raise Exception("Failed to set SSO client")
|
|
193
219
|
|
|
194
220
|
if rsp.app_id != self.client_info.app_id:
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
logger.error(f"Failed to update SSO client: {e}")
|
|
221
|
+
rsp = self.update_sso_client(address, rsp.client_id, self.client_info)
|
|
222
|
+
|
|
223
|
+
if not rsp.client_id or not rsp.client_secret:
|
|
224
|
+
raise ValueError("SSO response is missing clientId or clientSecret")
|
|
200
225
|
|
|
201
226
|
self.client_info = rsp
|
|
202
227
|
return None
|
|
@@ -217,9 +242,9 @@ def get_sso_client_id_and_secret(address: str, service: str) -> tuple:
|
|
|
217
242
|
try:
|
|
218
243
|
sso.set_sso_client(address)
|
|
219
244
|
except Exception as e:
|
|
220
|
-
raise ValueError(f"get sso clientId and ClientSecret err: {e}")
|
|
245
|
+
raise ValueError(f"get sso clientId and ClientSecret err: {e}") from e
|
|
221
246
|
|
|
222
|
-
logger.debug(
|
|
223
|
-
|
|
247
|
+
logger.debug("SSO credentials obtained for service %s, client ID %s",
|
|
248
|
+
sso.client_info.service_name, sso.client_info.client_id)
|
|
224
249
|
|
|
225
250
|
return sso.client_info.client_id, sso.client_info.client_secret
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sso_service.py
RENAMED
|
@@ -6,16 +6,18 @@ from typing import List, Optional, Dict, Any
|
|
|
6
6
|
|
|
7
7
|
import httpx
|
|
8
8
|
from agent_api_server.shared.request_context import outbound_headers
|
|
9
|
-
from .sdk import SSOClient
|
|
9
|
+
from .sdk import SSOClient
|
|
10
|
+
from .sdk.client import normalize_sso_address
|
|
10
11
|
|
|
11
12
|
logger = logging.getLogger(__name__)
|
|
12
13
|
|
|
13
14
|
|
|
14
15
|
class SSOConfig:
|
|
15
|
-
def __init__(self, sso_address: str, client_id: str, client_secret: str):
|
|
16
|
-
self.sso_address = sso_address
|
|
16
|
+
def __init__(self, sso_address: str, client_id: str, client_secret: str, service_name: str = ""):
|
|
17
|
+
self.sso_address = normalize_sso_address(sso_address)
|
|
17
18
|
self.client_id = client_id
|
|
18
19
|
self.client_secret = client_secret
|
|
20
|
+
self.service_name = service_name
|
|
19
21
|
|
|
20
22
|
|
|
21
23
|
def get_client_info_by_client_token(token: str) -> Dict[str, Any]:
|
|
@@ -60,11 +62,17 @@ class SSOService:
|
|
|
60
62
|
self.sso_config = sso_config
|
|
61
63
|
self.client_token = None
|
|
62
64
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
if bool(sso_config.client_id) != bool(sso_config.client_secret):
|
|
66
|
+
raise ValueError("CLIENT_ID and CLIENT_SECRET must be configured together")
|
|
67
|
+
|
|
68
|
+
self.sso_client = SSOClient(sso_config.service_name)
|
|
69
|
+
if sso_config.client_id and sso_config.client_secret:
|
|
70
|
+
self.sso_client.client_info.client_id = sso_config.client_id
|
|
71
|
+
self.sso_client.client_info.client_secret = sso_config.client_secret
|
|
72
|
+
else:
|
|
73
|
+
self.sso_client.set_sso_client(sso_config.sso_address)
|
|
74
|
+
self.sso_config.client_id = self.sso_client.client_info.client_id
|
|
75
|
+
self.sso_config.client_secret = self.sso_client.client_info.client_secret
|
|
68
76
|
|
|
69
77
|
def get_client_token(self) -> Optional[str]:
|
|
70
78
|
return self.client_token
|
|
@@ -172,7 +180,11 @@ class SSOService:
|
|
|
172
180
|
|
|
173
181
|
# 获取 JSON 数据
|
|
174
182
|
token_data = response.json()
|
|
175
|
-
|
|
183
|
+
access_token = token_data.get("accessToken")
|
|
184
|
+
if not isinstance(access_token, str) or not access_token:
|
|
185
|
+
raise ValueError("SSO response is missing accessToken")
|
|
186
|
+
self.client_token = access_token
|
|
187
|
+
return access_token
|
|
176
188
|
|
|
177
189
|
def get_client_id_and_secret(self) -> tuple[str, str]:
|
|
178
190
|
return self.sso_config.client_id, self.sso_config.client_secret
|
|
@@ -93,13 +93,17 @@ class ApplicationStartup:
|
|
|
93
93
|
credentials = ServiceHubCredentialLoader.load_credentials()
|
|
94
94
|
logger.info("Credentials loaded from ServiceHub")
|
|
95
95
|
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
postgres_url = credentials.to_postgres_dsn()
|
|
97
|
+
redis_url = credentials.to_redis_dsn()
|
|
98
|
+
storage = credentials.get_blobstore_s3_config() if credentials.blobstore else {}
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
global_config.POSTGRES_URL = postgres_url
|
|
101
|
+
global_config.REDIS_URL = redis_url
|
|
102
|
+
|
|
103
|
+
if storage:
|
|
104
|
+
global_config.STORAGE_ENDPOINT = storage["endpoint"]
|
|
105
|
+
global_config.ACCESS_KEY = storage["access_key"]
|
|
106
|
+
global_config.SECRET_KEY = storage["secret_key"]
|
|
103
107
|
|
|
104
108
|
logger.info("Credentials initialized successfully")
|
|
105
109
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/call_a2a_client.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/a2a_bridge/redis_task_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/acp_bridge/go/agent_test.go
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/dynamic_llm/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/dynamic_llm/dynamic_llm.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/listener/nats_listener.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/mcp_convert/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/mcp_convert/mcp_convert.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/agent_registry.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/modes/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/modes/chain.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/orchestrator/orchestrator.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/service_hub/service_hub_test.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/services/config_builder.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/services/stream_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/context_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/detect_message.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/get_model_info.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/markdown_fixer.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/model_manager_client.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/shared/request_context.py
RENAMED
|
File without changes
|
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/__init__.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/credential.py
RENAMED
|
File without changes
|
{agent_api_server-2.1.14 → agent_api_server-2.1.16}/agent_api_server/sso_service/sdk/encoding.py
RENAMED
|
File without changes
|