From f9320b2c91a4a57b1def886b9378782cb3bd8bb6 Mon Sep 17 00:00:00 2001 From: yyh <92089059+lyzno1@users.noreply.github.com> Date: Thu, 4 Jun 2026 16:27:37 +0800 Subject: [PATCH] fix(api): return agent timestamps as epoch seconds (#37057) Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- api/fields/agent_fields.py | 10 +++---- api/openapi/markdown/console-swagger.md | 18 +++++------ api/services/agent/composer_service.py | 3 +- api/services/agent/roster_service.py | 11 +++---- .../services/agent/test_agent_services.py | 30 ++++++++++++++++++- .../generated/api/console/agents/types.gen.ts | 18 +++++------ .../generated/api/console/agents/zod.gen.ts | 18 +++++------ .../generated/api/console/apps/types.gen.ts | 2 +- .../generated/api/console/apps/zod.gen.ts | 2 +- 9 files changed, 71 insertions(+), 41 deletions(-) diff --git a/api/fields/agent_fields.py b/api/fields/agent_fields.py index a25b5e67d3..cef5d9fc88 100644 --- a/api/fields/agent_fields.py +++ b/api/fields/agent_fields.py @@ -37,7 +37,7 @@ class AgentConfigSnapshotSummaryResponse(ResponseModel): summary: str | None = None version_note: str | None = None created_by: str | None = None - created_at: str | None = None + created_at: int | None = None class AgentRosterResponse(ResponseModel): @@ -59,9 +59,9 @@ class AgentRosterResponse(ResponseModel): created_by: str | None = None updated_by: str | None = None archived_by: str | None = None - archived_at: str | None = None - created_at: str | None = None - updated_at: str | None = None + archived_at: int | None = None + created_at: int | None = None + updated_at: int | None = None class AgentInviteOptionResponse(AgentRosterResponse): @@ -95,7 +95,7 @@ class AgentConfigRevisionResponse(ResponseModel): summary: str | None = None version_note: str | None = None created_by: str | None = None - created_at: str | None = None + created_at: int | None = None class AgentConfigSnapshotDetailResponse(AgentConfigSnapshotSummaryResponse): diff --git a/api/openapi/markdown/console-swagger.md b/api/openapi/markdown/console-swagger.md index fff00d80a8..755a665f4e 100644 --- a/api/openapi/markdown/console-swagger.md +++ b/api/openapi/markdown/console-swagger.md @@ -11058,7 +11058,7 @@ Audit operation recorded for Agent Soul version/revision changes. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | -| created_at | string | | No | +| created_at | integer | | No | | created_by | string | | No | | current_snapshot_id | string | | Yes | | id | string | | Yes | @@ -11074,7 +11074,7 @@ Audit operation recorded for Agent Soul version/revision changes. | ---- | ---- | ----------- | -------- | | agent_id | string | | No | | config_snapshot | [AgentSoulConfig](#agentsoulconfig) | | Yes | -| created_at | string | | No | +| created_at | integer | | No | | created_by | string | | No | | id | string | | Yes | | revisions | [ [AgentConfigRevisionResponse](#agentconfigrevisionresponse) ] | | No | @@ -11093,7 +11093,7 @@ Audit operation recorded for Agent Soul version/revision changes. | Name | Type | Description | Required | | ---- | ---- | ----------- | -------- | | agent_id | string | | No | -| created_at | string | | No | +| created_at | integer | | No | | created_by | string | | No | | id | string | | Yes | | summary | string | | No | @@ -11165,9 +11165,9 @@ Supported icon storage formats for Agent roster entries. | active_config_snapshot_id | string | | No | | agent_kind | [AgentKind](#agentkind) | | Yes | | app_id | string | | No | -| archived_at | string | | No | +| archived_at | integer | | No | | archived_by | string | | No | -| created_at | string | | No | +| created_at | integer | | No | | created_by | string | | No | | description | string | | Yes | | existing_node_ids | [ string ] | | No | @@ -11181,7 +11181,7 @@ Supported icon storage formats for Agent roster entries. | scope | [AgentScope](#agentscope) | | Yes | | source | [AgentSource](#agentsource) | | Yes | | status | [AgentStatus](#agentstatus) | | Yes | -| updated_at | string | | No | +| updated_at | integer | | No | | updated_by | string | | No | | workflow_id | string | | No | | workflow_node_id | string | | No | @@ -11311,9 +11311,9 @@ the current roster/workflow APIs scoped to Dify Agent. | active_config_snapshot_id | string | | No | | agent_kind | [AgentKind](#agentkind) | | Yes | | app_id | string | | No | -| archived_at | string | | No | +| archived_at | integer | | No | | archived_by | string | | No | -| created_at | string | | No | +| created_at | integer | | No | | created_by | string | | No | | description | string | | Yes | | icon | string | | No | @@ -11324,7 +11324,7 @@ the current roster/workflow APIs scoped to Dify Agent. | scope | [AgentScope](#agentscope) | | Yes | | source | [AgentSource](#agentsource) | | Yes | | status | [AgentStatus](#agentstatus) | | Yes | -| updated_at | string | | No | +| updated_at | integer | | No | | updated_by | string | | No | | workflow_id | string | | No | | workflow_node_id | string | | No | diff --git a/api/services/agent/composer_service.py b/api/services/agent/composer_service.py index a0f54a8a31..58500274d8 100644 --- a/api/services/agent/composer_service.py +++ b/api/services/agent/composer_service.py @@ -4,6 +4,7 @@ from sqlalchemy import func, select from sqlalchemy.exc import IntegrityError from extensions.ext_database import db +from libs.helper import to_timestamp from models.agent import ( Agent, AgentConfigRevision, @@ -802,7 +803,7 @@ class AgentComposerService: "version": version.version, "version_note": version.version_note, "created_by": version.created_by, - "created_at": version.created_at.isoformat() if version.created_at else None, + "created_at": to_timestamp(version.created_at), } @staticmethod diff --git a/api/services/agent/roster_service.py b/api/services/agent/roster_service.py index 35b3cbf868..c4ec16c0b7 100644 --- a/api/services/agent/roster_service.py +++ b/api/services/agent/roster_service.py @@ -4,6 +4,7 @@ from sqlalchemy import func, select from sqlalchemy.exc import IntegrityError from libs.datetime_utils import naive_utc_now +from libs.helper import to_timestamp from models.agent import ( Agent, AgentConfigRevision, @@ -64,9 +65,9 @@ class AgentRosterService: "created_by": agent.created_by, "updated_by": agent.updated_by, "archived_by": agent.archived_by, - "archived_at": agent.archived_at.isoformat() if agent.archived_at else None, - "created_at": agent.created_at.isoformat() if agent.created_at else None, - "updated_at": agent.updated_at.isoformat() if agent.updated_at else None, + "archived_at": to_timestamp(agent.archived_at), + "created_at": to_timestamp(agent.created_at), + "updated_at": to_timestamp(agent.updated_at), } @staticmethod @@ -80,7 +81,7 @@ class AgentRosterService: "summary": version.summary, "version_note": version.version_note, "created_by": version.created_by, - "created_at": version.created_at.isoformat() if version.created_at else None, + "created_at": to_timestamp(version.created_at), } def list_roster_agents( @@ -418,7 +419,7 @@ class AgentRosterService: "summary": revision.summary, "version_note": revision.version_note, "created_by": revision.created_by, - "created_at": revision.created_at.isoformat() if revision.created_at else None, + "created_at": to_timestamp(revision.created_at), } for revision in revisions ] diff --git a/api/tests/unit_tests/services/agent/test_agent_services.py b/api/tests/unit_tests/services/agent/test_agent_services.py index 8d97ff0c7d..fb0dfd1bea 100644 --- a/api/tests/unit_tests/services/agent/test_agent_services.py +++ b/api/tests/unit_tests/services/agent/test_agent_services.py @@ -1,3 +1,4 @@ +from datetime import UTC, datetime from types import SimpleNamespace import pytest @@ -495,6 +496,9 @@ def test_composer_current_version_and_error_paths(monkeypatch): def test_roster_list_and_invite_options(monkeypatch): + created_at = datetime(2026, 1, 2, 3, 4, 5, tzinfo=UTC) + updated_at = datetime(2026, 1, 3, 3, 4, 5, tzinfo=UTC) + version_created_at = datetime(2026, 1, 4, 3, 4, 5, tzinfo=UTC) agent = Agent( id="agent-1", tenant_id="tenant-1", @@ -505,7 +509,10 @@ def test_roster_list_and_invite_options(monkeypatch): source=AgentSource.AGENT_APP, status=AgentStatus.ACTIVE, ) + agent.created_at = created_at + agent.updated_at = updated_at version = AgentConfigSnapshot(id="version-1", agent_id="agent-1", version=1) + version.created_at = version_created_at agent.active_config_snapshot_id = "version-1" fake_session = FakeSession( scalar=[1, 1, SimpleNamespace(id="workflow-1")], @@ -518,13 +525,30 @@ def test_roster_list_and_invite_options(monkeypatch): invited = service.list_invite_options(tenant_id="tenant-1", page=1, limit=20, app_id="app-1") assert listed["data"][0]["active_config_snapshot"]["id"] == "version-1" + assert listed["data"][0]["created_at"] == int(created_at.timestamp()) + assert listed["data"][0]["updated_at"] == int(updated_at.timestamp()) + assert listed["data"][0]["active_config_snapshot"]["created_at"] == int(version_created_at.timestamp()) assert invited["data"][0]["is_in_current_workflow"] is True assert invited["data"][0]["existing_node_ids"] == ["node-1"] def test_roster_update_archive_versions_and_detail(monkeypatch): listed_version = AgentConfigSnapshot(id="version-2", agent_id="agent-1", version=2) - fake_session = FakeSession(scalars=[[listed_version]]) + listed_version_created_at = datetime(2026, 1, 5, 3, 4, 5, tzinfo=UTC) + listed_version.created_at = listed_version_created_at + revision_created_at = datetime(2026, 1, 6, 3, 4, 5, tzinfo=UTC) + revision = SimpleNamespace( + id="revision-1", + previous_snapshot_id=None, + current_snapshot_id="version-1", + revision=1, + operation=AgentConfigRevisionOperation.CREATE_VERSION, + summary=None, + version_note=None, + created_by="account-1", + created_at=revision_created_at, + ) + fake_session = FakeSession(scalars=[[listed_version], [revision]]) agent = Agent( id="agent-1", tenant_id="tenant-1", @@ -536,6 +560,7 @@ def test_roster_update_archive_versions_and_detail(monkeypatch): status=AgentStatus.ACTIVE, ) version = AgentConfigSnapshot(id="version-1", agent_id="agent-1", version=1, config_snapshot='{"prompt":{}}') + version.created_at = datetime(2026, 1, 4, 3, 4, 5, tzinfo=UTC) service = AgentRosterService(fake_session) monkeypatch.setattr(service, "_get_agent", lambda **kwargs: agent) @@ -559,7 +584,10 @@ def test_roster_update_archive_versions_and_detail(monkeypatch): assert updated["description"] == "new" assert agent.status == AgentStatus.ARCHIVED assert versions[0]["id"] == "version-2" + assert versions[0]["created_at"] == int(listed_version_created_at.timestamp()) assert detail["config_snapshot"] == {"prompt": {}} + assert detail["created_at"] == int(version.created_at.timestamp()) + assert detail["revisions"][0]["created_at"] == int(revision_created_at.timestamp()) def test_roster_create_detail_and_lookup_helpers(monkeypatch): diff --git a/packages/contracts/generated/api/console/agents/types.gen.ts b/packages/contracts/generated/api/console/agents/types.gen.ts index 1961f45cbf..06fb1aae6b 100644 --- a/packages/contracts/generated/api/console/agents/types.gen.ts +++ b/packages/contracts/generated/api/console/agents/types.gen.ts @@ -27,9 +27,9 @@ export type AgentRosterResponse = { active_config_snapshot_id?: string | null agent_kind: AgentKind app_id?: string | null - archived_at?: string | null + archived_at?: number | null archived_by?: string | null - created_at?: string | null + created_at?: number | null created_by?: string | null description: string icon?: string | null @@ -40,7 +40,7 @@ export type AgentRosterResponse = { scope: AgentScope source: AgentSource status: AgentStatus - updated_at?: string | null + updated_at?: number | null updated_by?: string | null workflow_id?: string | null workflow_node_id?: string | null @@ -69,7 +69,7 @@ export type AgentConfigSnapshotListResponse = { export type AgentConfigSnapshotDetailResponse = { agent_id?: string | null config_snapshot: AgentSoulConfig - created_at?: string | null + created_at?: number | null created_by?: string | null id: string revisions?: Array @@ -98,7 +98,7 @@ export type AgentIconType = 'emoji' | 'image' | 'link' export type AgentConfigSnapshotSummaryResponse = { agent_id?: string | null - created_at?: string | null + created_at?: number | null created_by?: string | null id: string summary?: string | null @@ -119,9 +119,9 @@ export type AgentInviteOptionResponse = { active_config_snapshot_id?: string | null agent_kind: AgentKind app_id?: string | null - archived_at?: string | null + archived_at?: number | null archived_by?: string | null - created_at?: string | null + created_at?: number | null created_by?: string | null description: string existing_node_ids?: Array @@ -135,14 +135,14 @@ export type AgentInviteOptionResponse = { scope: AgentScope source: AgentSource status: AgentStatus - updated_at?: string | null + updated_at?: number | null updated_by?: string | null workflow_id?: string | null workflow_node_id?: string | null } export type AgentConfigRevisionResponse = { - created_at?: string | null + created_at?: number | null created_by?: string | null current_snapshot_id: string id: string diff --git a/packages/contracts/generated/api/console/agents/zod.gen.ts b/packages/contracts/generated/api/console/agents/zod.gen.ts index fb24b8c350..2c6262c7c4 100644 --- a/packages/contracts/generated/api/console/agents/zod.gen.ts +++ b/packages/contracts/generated/api/console/agents/zod.gen.ts @@ -25,7 +25,7 @@ export const zRosterAgentUpdatePayload = z.object({ */ export const zAgentConfigSnapshotSummaryResponse = z.object({ agent_id: z.string().nullish(), - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), id: z.string(), summary: z.string().nullish(), @@ -79,9 +79,9 @@ export const zAgentRosterResponse = z.object({ active_config_snapshot_id: z.string().nullish(), agent_kind: zAgentKind, app_id: z.string().nullish(), - archived_at: z.string().nullish(), + archived_at: z.int().nullish(), archived_by: z.string().nullish(), - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), description: z.string(), icon: z.string().nullish(), @@ -92,7 +92,7 @@ export const zAgentRosterResponse = z.object({ scope: zAgentScope, source: zAgentSource, status: zAgentStatus, - updated_at: z.string().nullish(), + updated_at: z.int().nullish(), updated_by: z.string().nullish(), workflow_id: z.string().nullish(), workflow_node_id: z.string().nullish(), @@ -117,9 +117,9 @@ export const zAgentInviteOptionResponse = z.object({ active_config_snapshot_id: z.string().nullish(), agent_kind: zAgentKind, app_id: z.string().nullish(), - archived_at: z.string().nullish(), + archived_at: z.int().nullish(), archived_by: z.string().nullish(), - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), description: z.string(), existing_node_ids: z.array(z.string()).optional(), @@ -133,7 +133,7 @@ export const zAgentInviteOptionResponse = z.object({ scope: zAgentScope, source: zAgentSource, status: zAgentStatus, - updated_at: z.string().nullish(), + updated_at: z.int().nullish(), updated_by: z.string().nullish(), workflow_id: z.string().nullish(), workflow_node_id: z.string().nullish(), @@ -184,7 +184,7 @@ export const zAgentConfigRevisionOperation = z.enum([ * AgentConfigRevisionResponse */ export const zAgentConfigRevisionResponse = z.object({ - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), current_snapshot_id: z.string(), id: z.string(), @@ -591,7 +591,7 @@ export const zRosterAgentCreatePayload = z.object({ export const zAgentConfigSnapshotDetailResponse = z.object({ agent_id: z.string().nullish(), config_snapshot: zAgentSoulConfig, - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), id: z.string(), revisions: z.array(zAgentConfigRevisionResponse).optional(), diff --git a/packages/contracts/generated/api/console/apps/types.gen.ts b/packages/contracts/generated/api/console/apps/types.gen.ts index 6661a5c8be..09a3428f0b 100644 --- a/packages/contracts/generated/api/console/apps/types.gen.ts +++ b/packages/contracts/generated/api/console/apps/types.gen.ts @@ -1034,7 +1034,7 @@ export type AdvancedChatWorkflowRunForListResponse = { export type AgentConfigSnapshotSummaryResponse = { agent_id?: string | null - created_at?: string | null + created_at?: number | null created_by?: string | null id: string summary?: string | null diff --git a/packages/contracts/generated/api/console/apps/zod.gen.ts b/packages/contracts/generated/api/console/apps/zod.gen.ts index ee76b3a446..e5fb9ae2a4 100644 --- a/packages/contracts/generated/api/console/apps/zod.gen.ts +++ b/packages/contracts/generated/api/console/apps/zod.gen.ts @@ -753,7 +753,7 @@ export const zSite = z.object({ */ export const zAgentConfigSnapshotSummaryResponse = z.object({ agent_id: z.string().nullish(), - created_at: z.string().nullish(), + created_at: z.int().nullish(), created_by: z.string().nullish(), id: z.string(), summary: z.string().nullish(),