diff --git a/mem0-ts/src/client/index.ts b/mem0-ts/src/client/index.ts index 4b6b283df..2eef9cd9b 100644 --- a/mem0-ts/src/client/index.ts +++ b/mem0-ts/src/client/index.ts @@ -32,7 +32,6 @@ export type { EntityProfileSettings, ProfileSampleResult, ProfileSamplesResponse, - ProfileRegenerateResponse, } from "./mem0.types"; // Re-export enums as values (not type-only) diff --git a/mem0-ts/src/client/mem0.ts b/mem0-ts/src/client/mem0.ts index 1df9233cf..2116b8ec9 100644 --- a/mem0-ts/src/client/mem0.ts +++ b/mem0-ts/src/client/mem0.ts @@ -28,7 +28,6 @@ import { ProfileSettings, ProfileSettingsResponse, ProfileSamplesResponse, - ProfileRegenerateResponse, } from "./mem0.types"; import { captureClientEvent, @@ -772,21 +771,17 @@ export default class MemoryClient { } /** - * Get the memory profile for a single entity. + * Get the memory profile for a single user. * * Branch on `status`, not on an empty `profile`: generation is asynchronous, - * so a known entity without a profile yet is a normal response. + * so a known user without a profile yet is a normal response. */ - async getProfile(data: { - entityId: string; - entityType?: ProfileEntityType; - }): Promise { + async getProfile(data: { entityId: string }): Promise { this._captureEvent("get_profile", []); await this._awaitIdentity(); - const entityType = data.entityType ?? "user"; const response = await this._fetchWithErrorHandling( - `${this.host}/v2/entities/${encodeURIComponent(entityType)}/${encodeURIComponent(data.entityId)}/profile/`, + `${this.host}/v2/entities/user/${encodeURIComponent(data.entityId)}/profile/`, { headers: this.headers, }, @@ -795,15 +790,14 @@ export default class MemoryClient { } /** - * Generate or refresh the profile for one entity, now. + * Generate or refresh the profile for one user, now. * - * Profiles are otherwise built once an entity crosses an internal message - * threshold, so a new entity has none for its first few memories. Returns as + * Profiles are otherwise built once a user crosses an internal message + * threshold, so a new user has none for its first few memories. Returns as * soon as the work is queued: poll {@link getProfile} and branch on `status`. */ async generateProfile(data: { entityId: string; - entityType?: ProfileEntityType; }): Promise { this._captureEvent("generate_profile", []); await this._awaitIdentity(); @@ -815,7 +809,7 @@ export default class MemoryClient { headers: { ...this.headers, "Idempotency-Key": crypto.randomUUID() }, body: JSON.stringify({ operation: "trigger", - entity_type: data.entityType ?? "user", + entity_type: "user", entity_id: data.entityId, }), }, @@ -873,7 +867,7 @@ export default class MemoryClient { /** * Update profile settings. Only the fields you pass are written. * - * `schema` and `customInstructions` are per entity type and are nested under + * `schema` and `customInstructions` are per user and are nested under * `entities` for the API; only `enabled` is project-wide. Sending them flat * is rejected with `Unsupported settings`. */ @@ -884,12 +878,7 @@ export default class MemoryClient { this._captureEvent("update_profile_settings", [payloadKeys]); await this._awaitIdentity(); - const { - schema, - customInstructions, - enabled, - entityType = "user", - } = settings || {}; + const { schema, customInstructions, enabled } = settings || {}; const body: Record = {}; if (enabled !== undefined) { @@ -905,7 +894,7 @@ export default class MemoryClient { entitySettings.custom_instructions = customInstructions; } if (Object.keys(entitySettings).length > 0) { - body.entities = { [entityType]: entitySettings }; + body.entities = { user: entitySettings }; } const raw = await this._fetchRawJson(`${this.host}/v2/profiles/settings/`, { @@ -917,15 +906,12 @@ export default class MemoryClient { } /** - * Generate profiles for a few real entities, to check a schema. + * Generate profiles for a few real users, to check a schema. * * Real generations against real memories, and the results are kept: the - * profiles are written to those entities and count toward usage. + * profiles are written to those users and count toward usage. */ - async sampleProfiles(data?: { - limit?: number; - entityType?: ProfileEntityType; - }): Promise { + async sampleProfiles(data?: { limit?: number }): Promise { this._captureEvent("sample_profiles", []); await this._awaitIdentity(); @@ -937,7 +923,7 @@ export default class MemoryClient { body: JSON.stringify({ operation: "sample", // Required: the API refuses a job that does not name an entity kind. - entity_type: data?.entityType ?? "user", + entity_type: "user", ...this._prepareParams({ limit: data?.limit }), }), }, @@ -945,33 +931,6 @@ export default class MemoryClient { return response; } - /** - * Rebuild the profile of every entity of one kind in the project. - * - * Not available yet: the server answers 409 `not_yet_available` and creates - * nothing. Use {@link sampleProfiles} or {@link generateProfile} until - * `capabilities.full_rebuild` from {@link getProfileSettings} is true. - */ - async regenerateProfiles(data?: { - entityType?: ProfileEntityType; - }): Promise { - this._captureEvent("regenerate_profiles", []); - await this._awaitIdentity(); - - const response = await this._fetchWithErrorHandling( - `${this.host}${PROFILE_JOBS_PATH}`, - { - method: "POST", - headers: { ...this.headers, "Idempotency-Key": crypto.randomUUID() }, - body: JSON.stringify({ - operation: "regenerate", - entity_type: data?.entityType ?? "user", - }), - }, - ); - return response; - } - /** * Read one generation job. Accepts the `statusUrl` from a create call, or a * bare job id. Prefer `statusUrl` so a route change needs no client update. diff --git a/mem0-ts/src/client/mem0.types.ts b/mem0-ts/src/client/mem0.types.ts index 4327ce2f7..ce9a13734 100644 --- a/mem0-ts/src/client/mem0.types.ts +++ b/mem0-ts/src/client/mem0.types.ts @@ -239,8 +239,8 @@ export interface GetMemoryExportPayload { // ─── Profile Types ────────────────────────────────────────── -/** Entity kinds that can carry a profile. */ -export type ProfileEntityType = "user" | "agent"; +/** The entity kind that carries a profile. */ +export type ProfileEntityType = "user"; /** * `succeeded` is the only state in which `profile` is guaranteed to hold content. @@ -249,7 +249,11 @@ export type ProfileEntityType = "user" | "agent"; * spelling is what a comparison has to match. */ export type ProfileStatus = - "succeeded" | "pending" | "failed" | "not_enabled" | "insufficient_data"; + | "succeeded" + | "pending" + | "failed" + | "not_enabled" + | "insufficient_data"; export interface ProfileResponse { /** Shaped by the project's schema; keys are not camel-cased. */ @@ -282,15 +286,13 @@ export interface ProfileJobResponse { /** @deprecated Use {@link ProfileJobResponse}. */ export type ProfileTriggerResponse = ProfileJobResponse; -/** The settings to write. `schema` and `customInstructions` apply to one entity type. */ +/** The settings to write. `schema` and `customInstructions` apply to user profiles. */ export interface ProfileSettings { /** Turn profile generation on or off. Project-wide. */ enabled?: boolean; /** JSON Schema for the profile. Every property needs a `description`. */ schema?: Record | null; customInstructions?: string | null; - /** Which entity kind `schema` and `customInstructions` belong to. Defaults to "user". */ - entityType?: ProfileEntityType; } /** One entity type's stored configuration. */ @@ -322,9 +324,6 @@ export interface ProfileSampleResult { /** @deprecated Use {@link ProfileJobResponse}. */ export type ProfileSamplesResponse = ProfileJobResponse; -/** @deprecated Use {@link ProfileJobResponse}. */ -export type ProfileRegenerateResponse = ProfileJobResponse; - /** `GET /v2/profiles/jobs/{id}/`. The job nests under `job`. */ export interface ProfileJobStatus { job: { diff --git a/mem0-ts/src/client/tests/memoryClient.profiles.test.ts b/mem0-ts/src/client/tests/memoryClient.profiles.test.ts index 14f25d829..96decf113 100644 --- a/mem0-ts/src/client/tests/memoryClient.profiles.test.ts +++ b/mem0-ts/src/client/tests/memoryClient.profiles.test.ts @@ -76,18 +76,18 @@ describe("MemoryClient - getProfile()", () => { expect(status).not.toBe("insufficientData"); }); - test("defaults to user and encodes the entity id", async () => { + test("scopes to user and encodes the entity id", async () => { const extra = new Map(); - extra.set("/v2/entities/agent/", { + extra.set("/v2/entities/user/", { status: 200, - body: { profile: {}, status: "pending", entity_type: "agent" }, + body: { profile: {}, status: "pending", entity_type: "user" }, }); const mock = setupMockFetch(extra); const client = new MemoryClient({ apiKey: TEST_API_KEY }); - await client.getProfile({ entityId: "a/b", entityType: "agent" }); + await client.getProfile({ entityId: "a/b" }); - const call = findFetchCall(mock, "/v2/entities/agent/a%2Fb/profile/"); + const call = findFetchCall(mock, "/v2/entities/user/a%2Fb/profile/"); expect(call).toBeDefined(); }); }); @@ -179,7 +179,7 @@ describe("MemoryClient - profile settings", () => { ); }); - test("targets the entity type the caller named", async () => { + test("scopes entity-level settings under user", async () => { const extra = new Map(); extra.set("/v2/profiles/settings/", { status: 200, @@ -190,13 +190,12 @@ describe("MemoryClient - profile settings", () => { const client = new MemoryClient({ apiKey: TEST_API_KEY }); await client.updateProfileSettings({ schema: { type: "object", properties: {} }, - entityType: "agent", }); const body = getFetchBody( findFetchCall(mock, "/v2/profiles/settings/", "POST")!, ); - expect(Object.keys(body.entities)).toEqual(["agent"]); + expect(Object.keys(body.entities)).toEqual(["user"]); }); test("omits fields the caller did not set", async () => { @@ -251,7 +250,7 @@ describe("MemoryClient - profile settings", () => { }); }); -describe("MemoryClient - sampleProfiles() / regenerateProfiles()", () => { +describe("MemoryClient - sampleProfiles()", () => { test("sampleProfiles omits limit when unset", async () => { const extra = new Map(); extra.set("/v2/profiles/jobs/", { @@ -304,46 +303,4 @@ describe("MemoryClient - sampleProfiles() / regenerateProfiles()", () => { expect(getFetchBody(call!).entity_type).toBe("user"); expect(result.sampled).toBe(3); }); - - test("sampleProfiles targets the entity type the caller named", async () => { - const extra = new Map(); - extra.set("/v2/profiles/jobs/", { - status: 202, - body: { job_id: "job_4", status: "QUEUED", entity_type: "agent" }, - }); - const mock = setupMockFetch(extra); - - const client = new MemoryClient({ apiKey: TEST_API_KEY }); - await client.sampleProfiles({ entityType: "agent" }); - - const call = findFetchCall(mock, "/v2/profiles/jobs/", "POST"); - expect(getFetchBody(call!).entity_type).toBe("agent"); - }); - - test("sends operation regenerate to the jobs collection", async () => { - const extra = new Map(); - extra.set("/v2/profiles/jobs/", { - status: 202, - body: { - job_id: "job_3", - status: "QUEUED", - status_url: "/v2/profiles/jobs/job_3/", - operation: "regenerate", - entity_type: "user", - }, - }); - const mock = setupMockFetch(extra); - - const client = new MemoryClient({ apiKey: TEST_API_KEY }); - const result = await client.regenerateProfiles(); - - const call = findFetchCall(mock, "/v2/profiles/jobs/", "POST"); - expect(call).toBeDefined(); - expect(getFetchBody(call!)).toEqual({ - operation: "regenerate", - entity_type: "user", - }); - expect(result.jobId).toBe("job_3"); - expect(result.statusUrl).toBe("/v2/profiles/jobs/job_3/"); - }); }); diff --git a/mem0/client/main.py b/mem0/client/main.py index be4666474..4863df3ad 100644 --- a/mem0/client/main.py +++ b/mem0/client/main.py @@ -49,11 +49,10 @@ def _profile_settings_payload( enabled: Optional[bool], schema: Optional[Dict[str, Any]], custom_instructions: Optional[str], - entity_type: str, ) -> Dict[str, Any]: """Build the settings body the API accepts. - ``schema`` and ``custom_instructions`` are per entity type and nest under + ``schema`` and ``custom_instructions`` are per user and nest under ``entities``; only ``enabled`` is project-wide. This mirrors what ``get_profile_settings`` returns, so the two round-trip. @@ -72,7 +71,7 @@ def _profile_settings_payload( entity_settings["custom_instructions"] = custom_instructions if entity_settings: - payload["entities"] = {entity_type: entity_settings} + payload["entities"] = {"user": entity_settings} return payload @@ -719,15 +718,14 @@ class MemoryClient: return response.json() @api_error_handler - def get_profile(self, entity_id: str, entity_type: str = "user") -> Dict[str, Any]: - """Get the memory profile for a single entity. + def get_profile(self, entity_id: str) -> Dict[str, Any]: + """Get the memory profile for a single user. Branch on ``status``, not on an empty ``profile``: generation is - asynchronous, so a known entity without a profile yet is a normal response. + asynchronous, so a known user without a profile yet is a normal response. Args: - entity_id: The entity's id, as you supplied it on ``add`` (e.g. "alice"). - entity_type: Either "user" or "agent". Defaults to "user". + entity_id: The user's id, as you supplied it on ``add`` (e.g. "alice"). Returns: Dict with ``profile``, ``status``, ``entity_type``, ``entity_id``, @@ -735,47 +733,43 @@ class MemoryClient: "succeeded", "pending", "failed", "not_enabled" or "insufficient_data". Raises: - ValidationError: If entity_type is not a supported entity kind. AuthenticationError: If authentication fails. - NotFoundError: If no such entity exists in the project. + NotFoundError: If no such user exists in the project. """ - response = self.client.get( - f"/v2/entities/{_encode_path_segment(entity_type)}/{_encode_path_segment(entity_id)}/profile/" - ) + response = self.client.get(f"/v2/entities/user/{_encode_path_segment(entity_id)}/profile/") response.raise_for_status() - capture_client_event("client.get_profile", self, {"entity_type": entity_type, "sync_type": "sync"}) + capture_client_event("client.get_profile", self, {"sync_type": "sync"}) return response.json() @api_error_handler - def generate_profile(self, entity_id: str, entity_type: str = "user") -> Dict[str, Any]: - """Generate or refresh the profile for a single entity, now. + def generate_profile(self, entity_id: str) -> Dict[str, Any]: + """Generate or refresh the profile for a single user, now. - Profiles are otherwise built once an entity crosses an internal message - threshold, so a new entity has none for its first few memories. Returns as + Profiles are otherwise built once a user crosses an internal message + threshold, so a new user has none for its first few memories. Returns as soon as the work is queued: poll :meth:`get_profile` and branch on ``status``. Args: - entity_id: The entity's id, as you supplied it on ``add``. - entity_type: Either "user" or "agent". Defaults to "user". + entity_id: The user's id, as you supplied it on ``add``. Returns: Dict containing ``profile_id``, ``entity_type``, ``entity_id`` and ``status``. Raises: - ValidationError: If entity_type is unsupported or profiles are not - enabled and configured for the project. - NotFoundError: If no such entity exists in the project. + ValidationError: If profiles are not enabled and configured for the + project. + NotFoundError: If no such user exists in the project. """ response = self.client.post( PROFILE_JOBS_PATH, - json={"operation": "trigger", "entity_type": entity_type, "entity_id": entity_id}, + json={"operation": "trigger", "entity_type": "user", "entity_id": entity_id}, headers={"Idempotency-Key": uuid.uuid4().hex}, ) response.raise_for_status() - capture_client_event("client.generate_profile", self, {"entity_type": entity_type, "sync_type": "sync"}) + capture_client_event("client.generate_profile", self, {"sync_type": "sync"}) return response.json() @api_error_handler @@ -799,7 +793,6 @@ class MemoryClient: enabled: Optional[bool] = None, schema: Optional[Dict[str, Any]] = None, custom_instructions: Optional[str] = None, - entity_type: str = "user", ) -> Dict[str, Any]: """Update the profile settings for the current project. @@ -808,11 +801,9 @@ class MemoryClient: Args: enabled: Turn profile generation on or off. Project-wide. schema: JSON Schema for the profile. Every property needs a - ``description``. Applies to ``entity_type``. + ``description``. Applies to user profiles. custom_instructions: Extra guidance for the extraction step. - Applies to ``entity_type``. - entity_type: Which entity kind ``schema`` and - ``custom_instructions`` belong to. Defaults to "user". + Applies to user profiles. Returns: Dict with the settings as stored after the update, in the same @@ -822,26 +813,25 @@ class MemoryClient: ValidationError: If the schema is not a valid profile schema. """ - payload = _profile_settings_payload(enabled, schema, custom_instructions, entity_type) + payload = _profile_settings_payload(enabled, schema, custom_instructions) response = self.client.post(PROFILE_SETTINGS_PATH, json=payload) response.raise_for_status() capture_client_event( "client.update_profile_settings", self, - {"keys": list(payload.keys()), "entity_type": entity_type, "sync_type": "sync"}, + {"keys": list(payload.keys()), "sync_type": "sync"}, ) return response.json() @api_error_handler - def sample_profiles(self, limit: Optional[int] = None, entity_type: str = "user") -> Dict[str, Any]: - """Generate profiles for a few real entities, to check a schema. + def sample_profiles(self, limit: Optional[int] = None) -> Dict[str, Any]: + """Generate profiles for a few real users, to check a schema. Real generations against real memories, and the results are kept. The - profiles are written to those entities and count toward usage. + profiles are written to those users and count toward usage. Args: - limit: How many entities to sample, 1-10. Defaults to the server value. - entity_type: Which entity kind to sample. Defaults to "user". + limit: How many users to sample, 1-10. Defaults to the server value. Returns: Dict containing ``job_id``, ``status``, ``status_url``, ``sampled`` @@ -855,42 +845,14 @@ class MemoryClient: payload = self._prepare_params({"limit": limit}) payload["operation"] = "sample" - payload["entity_type"] = entity_type + payload["entity_type"] = "user" response = self.client.post( PROFILE_JOBS_PATH, json=payload, headers={"Idempotency-Key": uuid.uuid4().hex}, ) response.raise_for_status() - capture_client_event("client.sample_profiles", self, {"entity_type": entity_type, "sync_type": "sync"}) - return response.json() - - @api_error_handler - def regenerate_profiles(self, entity_type: str = "user") -> Dict[str, Any]: - """Rebuild the profile of every entity of one kind in the project. - - Not available yet: the server answers 409 with ``not_yet_available`` and - creates nothing. Use :meth:`sample_profiles` or :meth:`generate_profile` - until ``capabilities.full_rebuild`` in :meth:`get_profile_settings` is true. - - Args: - entity_type: Which entity kind to rebuild. Defaults to "user". - - Returns: - Dict containing ``job_id``, ``status`` and ``status_url``. - - Raises: - ValidationError: If profiles are not enabled and configured. - RateLimitError: If a regenerate already ran for this project recently. - """ - - response = self.client.post( - PROFILE_JOBS_PATH, - json={"operation": "regenerate", "entity_type": entity_type}, - headers={"Idempotency-Key": uuid.uuid4().hex}, - ) - response.raise_for_status() - capture_client_event("client.regenerate_profiles", self, {"entity_type": entity_type, "sync_type": "sync"}) + capture_client_event("client.sample_profiles", self, {"sync_type": "sync"}) return response.json() @api_error_handler @@ -1820,15 +1782,14 @@ class AsyncMemoryClient: return response.json() @api_error_handler - async def get_profile(self, entity_id: str, entity_type: str = "user") -> Dict[str, Any]: - """Get the memory profile for a single entity. + async def get_profile(self, entity_id: str) -> Dict[str, Any]: + """Get the memory profile for a single user. Branch on ``status``, not on an empty ``profile``: generation is - asynchronous, so a known entity without a profile yet is a normal response. + asynchronous, so a known user without a profile yet is a normal response. Args: - entity_id: The entity's id, as you supplied it on ``add`` (e.g. "alice"). - entity_type: Either "user" or "agent". Defaults to "user". + entity_id: The user's id, as you supplied it on ``add`` (e.g. "alice"). Returns: Dict with ``profile``, ``status``, ``entity_type``, ``entity_id``, @@ -1836,47 +1797,43 @@ class AsyncMemoryClient: "succeeded", "pending", "failed", "not_enabled" or "insufficient_data". Raises: - ValidationError: If entity_type is not a supported entity kind. AuthenticationError: If authentication fails. - NotFoundError: If no such entity exists in the project. + NotFoundError: If no such user exists in the project. """ - response = await self.async_client.get( - f"/v2/entities/{_encode_path_segment(entity_type)}/{_encode_path_segment(entity_id)}/profile/" - ) + response = await self.async_client.get(f"/v2/entities/user/{_encode_path_segment(entity_id)}/profile/") response.raise_for_status() - capture_client_event("client.get_profile", self, {"entity_type": entity_type, "sync_type": "async"}) + capture_client_event("client.get_profile", self, {"sync_type": "async"}) return response.json() @api_error_handler - async def generate_profile(self, entity_id: str, entity_type: str = "user") -> Dict[str, Any]: - """Generate or refresh the profile for a single entity, now. + async def generate_profile(self, entity_id: str) -> Dict[str, Any]: + """Generate or refresh the profile for a single user, now. - Profiles are otherwise built once an entity crosses an internal message - threshold, so a new entity has none for its first few memories. Returns as + Profiles are otherwise built once a user crosses an internal message + threshold, so a new user has none for its first few memories. Returns as soon as the work is queued: poll :meth:`get_profile` and branch on ``status``. Args: - entity_id: The entity's id, as you supplied it on ``add``. - entity_type: Either "user" or "agent". Defaults to "user". + entity_id: The user's id, as you supplied it on ``add``. Returns: Dict containing ``profile_id``, ``entity_type``, ``entity_id`` and ``status``. Raises: - ValidationError: If entity_type is unsupported or profiles are not - enabled and configured for the project. - NotFoundError: If no such entity exists in the project. + ValidationError: If profiles are not enabled and configured for the + project. + NotFoundError: If no such user exists in the project. """ response = await self.async_client.post( PROFILE_JOBS_PATH, - json={"operation": "trigger", "entity_type": entity_type, "entity_id": entity_id}, + json={"operation": "trigger", "entity_type": "user", "entity_id": entity_id}, headers={"Idempotency-Key": uuid.uuid4().hex}, ) response.raise_for_status() - capture_client_event("client.generate_profile", self, {"entity_type": entity_type, "sync_type": "async"}) + capture_client_event("client.generate_profile", self, {"sync_type": "async"}) return response.json() @api_error_handler @@ -1900,7 +1857,6 @@ class AsyncMemoryClient: enabled: Optional[bool] = None, schema: Optional[Dict[str, Any]] = None, custom_instructions: Optional[str] = None, - entity_type: str = "user", ) -> Dict[str, Any]: """Update the profile settings for the current project. @@ -1909,11 +1865,9 @@ class AsyncMemoryClient: Args: enabled: Turn profile generation on or off. Project-wide. schema: JSON Schema for the profile. Every property needs a - ``description``. Applies to ``entity_type``. + ``description``. Applies to user profiles. custom_instructions: Extra guidance for the extraction step. - Applies to ``entity_type``. - entity_type: Which entity kind ``schema`` and - ``custom_instructions`` belong to. Defaults to "user". + Applies to user profiles. Returns: Dict with the settings as stored after the update, in the same @@ -1923,26 +1877,25 @@ class AsyncMemoryClient: ValidationError: If the schema is not a valid profile schema. """ - payload = _profile_settings_payload(enabled, schema, custom_instructions, entity_type) + payload = _profile_settings_payload(enabled, schema, custom_instructions) response = await self.async_client.post(PROFILE_SETTINGS_PATH, json=payload) response.raise_for_status() capture_client_event( "client.update_profile_settings", self, - {"keys": list(payload.keys()), "entity_type": entity_type, "sync_type": "async"}, + {"keys": list(payload.keys()), "sync_type": "async"}, ) return response.json() @api_error_handler - async def sample_profiles(self, limit: Optional[int] = None, entity_type: str = "user") -> Dict[str, Any]: - """Generate profiles for a few real entities, to check a schema. + async def sample_profiles(self, limit: Optional[int] = None) -> Dict[str, Any]: + """Generate profiles for a few real users, to check a schema. Real generations against real memories, and the results are kept. The - profiles are written to those entities and count toward usage. + profiles are written to those users and count toward usage. Args: - limit: How many entities to sample, 1-10. Defaults to the server value. - entity_type: Which entity kind to sample. Defaults to "user". + limit: How many users to sample, 1-10. Defaults to the server value. Returns: Dict containing ``job_id``, ``status``, ``status_url``, ``sampled`` @@ -1956,44 +1909,14 @@ class AsyncMemoryClient: payload = self._prepare_params({"limit": limit}) payload["operation"] = "sample" - payload["entity_type"] = entity_type + payload["entity_type"] = "user" response = await self.async_client.post( PROFILE_JOBS_PATH, json=payload, headers={"Idempotency-Key": uuid.uuid4().hex}, ) response.raise_for_status() - capture_client_event("client.sample_profiles", self, {"entity_type": entity_type, "sync_type": "async"}) - return response.json() - - @api_error_handler - async def regenerate_profiles(self, entity_type: str = "user") -> Dict[str, Any]: - """Rebuild the profile of every entity of one kind in the project. - - Not available yet: the server answers 409 with ``not_yet_available`` and - creates nothing. Use :meth:`sample_profiles` or :meth:`generate_profile` - until ``capabilities.full_rebuild`` in :meth:`get_profile_settings` is true. - - Args: - entity_type: Which entity kind to rebuild. Defaults to "user". - - Returns: - Dict containing ``job_id``, ``status`` and ``status_url``. - - Raises: - ValidationError: If profiles are not enabled and configured. - RateLimitError: If a regenerate already ran for this project recently. - """ - - response = await self.async_client.post( - PROFILE_JOBS_PATH, - json={"operation": "regenerate", "entity_type": entity_type}, - headers={"Idempotency-Key": uuid.uuid4().hex}, - ) - response.raise_for_status() - capture_client_event( - "client.regenerate_profiles", self, {"entity_type": entity_type, "sync_type": "async"} - ) + capture_client_event("client.sample_profiles", self, {"sync_type": "async"}) return response.json() @api_error_handler diff --git a/tests/test_client_profiles.py b/tests/test_client_profiles.py index 3b425cd6d..ea0ae5850 100644 --- a/tests/test_client_profiles.py +++ b/tests/test_client_profiles.py @@ -57,13 +57,6 @@ class TestGetProfile: mock_memory_client.client.get.assert_called_once_with("/v2/entities/user/alice/profile/") - def test_supports_agents(self, mock_memory_client): - mock_memory_client.client.get.return_value = _mock_response({"profile": {}, "status": "pending"}) - - mock_memory_client.get_profile("support-bot", entity_type="agent") - - mock_memory_client.client.get.assert_called_once_with("/v2/entities/agent/support-bot/profile/") - def test_encodes_path_segments(self, mock_memory_client): """An id with a slash must not open a new path segment.""" mock_memory_client.client.get.return_value = _mock_response({"profile": {}, "status": "pending"}) @@ -98,16 +91,6 @@ class TestGenerateProfile: {"operation": "trigger", "entity_type": "user", "entity_id": "alice"}, ) - def test_agent_entity_type(self, mock_memory_client): - mock_memory_client.client.post.return_value = _mock_response({"profile_id": "p_2", "status": "PENDING"}) - - mock_memory_client.generate_profile("support-bot", entity_type="agent") - - _assert_job_call( - mock_memory_client.client.post, - {"operation": "trigger", "entity_type": "agent", "entity_id": "support-bot"}, - ) - class TestProfileSettings: def test_get_reads_v2(self, mock_memory_client): @@ -145,14 +128,14 @@ class TestProfileSettings: assert set(kwargs["json"]) == {"enabled", "entities"} assert "schema" not in kwargs["json"] - def test_update_targets_the_named_entity_type(self, mock_memory_client): + def test_update_targets_the_user_entity_type(self, mock_memory_client): schema = {"type": "object", "properties": {"x": {"type": "string", "description": "d"}}} mock_memory_client.client.post.return_value = _mock_response({"enabled": True}) - mock_memory_client.update_profile_settings(schema=schema, entity_type="agent") + mock_memory_client.update_profile_settings(schema=schema) _, kwargs = mock_memory_client.client.post.call_args - assert kwargs["json"] == {"entities": {"agent": {"schema": schema}}} + assert kwargs["json"] == {"entities": {"user": {"schema": schema}}} def test_update_passes_schema_verbatim(self, mock_memory_client): schema = { @@ -178,7 +161,7 @@ class TestProfileSettings: ) -class TestSampleAndRegenerate: +class TestSampleProfiles: def test_sample_without_limit(self, mock_memory_client): mock_memory_client.client.post.return_value = _mock_response({"sampled": 5, "entity_ids": []}) @@ -196,23 +179,6 @@ class TestSampleAndRegenerate: {"operation": "sample", "limit": 3, "entity_type": "user"}, ) - def test_sample_names_the_entity_type(self, mock_memory_client): - """Every job names an entity kind: the API refuses one that does not.""" - mock_memory_client.client.post.return_value = _mock_response({"sampled": 1, "entity_ids": []}) - - mock_memory_client.sample_profiles(entity_type="agent") - - _assert_job_call(mock_memory_client.client.post, {"operation": "sample", "entity_type": "agent"}) - - def test_regenerate(self, mock_memory_client): - mock_memory_client.client.post.return_value = _mock_response( - {"status": "accepted", "project_id": "proj_abc", "existing_profile_count": 12} - ) - - mock_memory_client.regenerate_profiles() - - _assert_job_call(mock_memory_client.client.post, {"operation": "regenerate", "entity_type": "user"}) - class TestAsyncClientParity: """The async client must speak the same wire protocol as the sync one.""" @@ -246,11 +212,11 @@ class TestAsyncClientParity: def test_generate_profile(self, async_client): async_client.async_client.post = AsyncMock(return_value=_mock_response({"profile_id": "p_1"})) - asyncio.run(async_client.generate_profile("alice", entity_type="agent")) + asyncio.run(async_client.generate_profile("alice")) _assert_job_call( async_client.async_client.post, - {"operation": "trigger", "entity_type": "agent", "entity_id": "alice"}, + {"operation": "trigger", "entity_type": "user", "entity_id": "alice"}, ) def test_update_settings_partial(self, async_client): @@ -274,10 +240,3 @@ class TestAsyncClientParity: "/v2/profiles/settings/", json={"enabled": True, "entities": {"user": {"schema": schema}}}, ) - - def test_regenerate(self, async_client): - async_client.async_client.post = AsyncMock(return_value=_mock_response({"status": "accepted"})) - - asyncio.run(async_client.regenerate_profiles()) - - _assert_job_call(async_client.async_client.post, {"operation": "regenerate", "entity_type": "user"})