diff --git a/litellm/llms/chatgpt/db_authenticator.py b/litellm/llms/chatgpt/db_authenticator.py index 46f6630bb7c..e7098878cc5 100644 --- a/litellm/llms/chatgpt/db_authenticator.py +++ b/litellm/llms/chatgpt/db_authenticator.py @@ -176,6 +176,7 @@ async def persist_credential_to_db(item: CredentialItem) -> None: # would bind the stale None reference rather than the live client. from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper from litellm.proxy.proxy_server import prisma_client + from litellm.proxy.utils import jsonify_object if prisma_client is None: verbose_logger.debug( @@ -191,19 +192,25 @@ async def persist_credential_to_db(item: CredentialItem) -> None: "type": CREDENTIAL_TYPE, "custom_llm_provider": "chatgpt", } + # Prisma's Json columns want JSON-serialized strings, not raw dicts — + # see credential_endpoints/endpoints.py for the canonical pattern. + jsonified = jsonify_object( + { + "credential_values": encrypted_values, + "credential_info": credential_info, + } + ) await prisma_client.db.litellm_credentialstable.upsert( where={"credential_name": item.credential_name}, data={ "create": { "credential_name": item.credential_name, - "credential_values": encrypted_values, - "credential_info": credential_info, + **jsonified, "created_by": "chatgpt_oauth_flow", "updated_by": "chatgpt_oauth_flow", }, "update": { - "credential_values": encrypted_values, - "credential_info": credential_info, + **jsonified, "updated_by": "chatgpt_oauth_flow", }, }, diff --git a/litellm/llms/github_copilot/db_authenticator.py b/litellm/llms/github_copilot/db_authenticator.py index 4d1ddf5e703..8ba126b896f 100644 --- a/litellm/llms/github_copilot/db_authenticator.py +++ b/litellm/llms/github_copilot/db_authenticator.py @@ -180,6 +180,7 @@ async def persist_credential_to_db(item: CredentialItem) -> None: # would bind the stale None reference rather than the live client. from litellm.proxy.common_utils.encrypt_decrypt_utils import encrypt_value_helper from litellm.proxy.proxy_server import prisma_client + from litellm.proxy.utils import jsonify_object if prisma_client is None: verbose_logger.debug( @@ -195,19 +196,24 @@ async def persist_credential_to_db(item: CredentialItem) -> None: "type": CREDENTIAL_TYPE, "custom_llm_provider": "github_copilot", } + # Prisma's Json columns want JSON-serialized strings, not raw dicts. + jsonified = jsonify_object( + { + "credential_values": encrypted_values, + "credential_info": credential_info, + } + ) await prisma_client.db.litellm_credentialstable.upsert( where={"credential_name": item.credential_name}, data={ "create": { "credential_name": item.credential_name, - "credential_values": encrypted_values, - "credential_info": credential_info, + **jsonified, "created_by": "copilot_oauth_flow", "updated_by": "copilot_oauth_flow", }, "update": { - "credential_values": encrypted_values, - "credential_info": credential_info, + **jsonified, "updated_by": "copilot_oauth_flow", }, }, diff --git a/tests/test_litellm/llms/chatgpt/test_chatgpt_db_authenticator.py b/tests/test_litellm/llms/chatgpt/test_chatgpt_db_authenticator.py index aa950abd68d..5d620e1844e 100644 --- a/tests/test_litellm/llms/chatgpt/test_chatgpt_db_authenticator.py +++ b/tests/test_litellm/llms/chatgpt/test_chatgpt_db_authenticator.py @@ -1,3 +1,4 @@ +import json from unittest.mock import MagicMock, patch import pytest @@ -178,11 +179,12 @@ class TestPersistCredentialToDb: assert kwargs["where"] == {"credential_name": "test"} create = kwargs["data"]["create"] assert create["credential_name"] == "test" - assert create["credential_values"] == { + # Prisma Json columns receive pre-serialized JSON strings. + assert json.loads(create["credential_values"]) == { "access_token": "enc(a)", "refresh_token": "enc(r)", } - assert create["credential_info"] == {"type": CREDENTIAL_TYPE} + assert json.loads(create["credential_info"]) == {"type": CREDENTIAL_TYPE} update = kwargs["data"]["update"] assert update["credential_values"] == create["credential_values"] diff --git a/tests/test_litellm/llms/github_copilot/test_copilot_db_authenticator.py b/tests/test_litellm/llms/github_copilot/test_copilot_db_authenticator.py index 95877f8d782..ba0a676b679 100644 --- a/tests/test_litellm/llms/github_copilot/test_copilot_db_authenticator.py +++ b/tests/test_litellm/llms/github_copilot/test_copilot_db_authenticator.py @@ -1,3 +1,4 @@ +import json from unittest.mock import MagicMock, patch import pytest @@ -225,10 +226,14 @@ class TestPersistCredentialToDb: fake_prisma.db.litellm_credentialstable.upsert.assert_called_once() kwargs = fake_prisma.db.litellm_credentialstable.upsert.call_args.kwargs assert kwargs["where"] == {"credential_name": "c"} - assert kwargs["data"]["create"]["credential_values"] == { + # Prisma Json columns receive pre-serialized JSON strings. + assert json.loads(kwargs["data"]["create"]["credential_values"]) == { "access_token": "enc(gho_abc)" } - assert kwargs["data"]["create"]["credential_info"]["type"] == CREDENTIAL_TYPE + assert ( + json.loads(kwargs["data"]["create"]["credential_info"])["type"] + == CREDENTIAL_TYPE + ) class _Awaitable: