Spaces:
Running
Running
| import type { AgentImageResponse } from './agent-api-contracts'; | |
| import { MemoryAgentStateStore } from './agent-state-memory'; | |
| import { hashAgentPayload, type AgentArtifactRecord } from './agent-state-store'; | |
| import type { FeedbackRecord } from './feedback-store'; | |
| import assert from 'node:assert/strict'; | |
| import crypto from 'node:crypto'; | |
| import { access, mkdir, rm, writeFile } from 'node:fs/promises'; | |
| import path from 'node:path'; | |
| import { describe, it } from 'node:test'; | |
| describe('MemoryAgentStateStore', () => { | |
| it('acquires, completes, and replays idempotent requests', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory image' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-ok', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const response: AgentImageResponse = { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-memory-ok', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }; | |
| const artifact = buildArtifact({ id: 'artifact-memory-ok', requestId: begin.record.requestId }); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response, | |
| artifacts: [artifact], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| const replay = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-ok', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:01.000Z') | |
| }); | |
| assert.equal(replay.type, 'replay'); | |
| if (replay.type !== 'replay') throw new Error('expected replay'); | |
| assert.deepEqual(replay.response, response); | |
| assert.deepEqual(await store.getArtifact('artifact-memory-ok'), artifact); | |
| }); | |
| it('recovers expired running requests with artifact metadata as succeeded', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory recovery' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-recovery', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| await store.saveArtifacts([ | |
| buildArtifact({ id: 'artifact-memory-recovery', requestId: begin.record.requestId }) | |
| ]); | |
| const recovered = await store.recoverExpiredRequests(new Date('2026-05-12T00:00:01.000Z')); | |
| const replay = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-recovery', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:02.000Z') | |
| }); | |
| assert.equal(recovered, 1); | |
| assert.equal(replay.type, 'replay'); | |
| }); | |
| it('keeps refreshed running requests out of recovery', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory lease refresh' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-refresh', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| assert.equal( | |
| await store.refreshRequestLease({ | |
| requestId: begin.record.requestId, | |
| leaseMs: 1000, | |
| now: new Date('2026-05-12T00:00:00.050Z') | |
| }), | |
| true | |
| ); | |
| assert.equal(await store.recoverExpiredRequests(new Date('2026-05-12T00:00:00.200Z')), 0); | |
| const retry = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-refresh', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 100, | |
| ttlSeconds: 60, | |
| now: new Date('2026-05-12T00:00:00.200Z') | |
| }); | |
| assert.equal(retry.type, 'in_progress'); | |
| }); | |
| it('rejects artifact metadata for unknown requests', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| await assert.rejects( | |
| () => store.saveArtifacts([buildArtifact({ id: 'artifact-memory-orphan', requestId: 'missing-request' })]), | |
| /FOREIGN KEY/ | |
| ); | |
| }); | |
| it('lists sorted artifact filepaths for cleanup protection', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory cleanup protection' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-cleanup-protection', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const firstPath = path.join(process.cwd(), 'generated-images', 'cleanup-a.png'); | |
| const secondPath = path.join(process.cwd(), 'generated-images', 'cleanup-b.png'); | |
| await store.saveArtifacts([ | |
| buildArtifact({ | |
| id: 'artifact-memory-cleanup-b', | |
| requestId: begin.record.requestId, | |
| filepath: secondPath | |
| }), | |
| buildArtifact({ | |
| id: 'artifact-memory-cleanup-a', | |
| requestId: begin.record.requestId, | |
| filepath: firstPath | |
| }) | |
| ]); | |
| assert.deepEqual(await store.listArtifactFilepaths(), [firstPath, secondPath]); | |
| }); | |
| it('upserts and reads result feedback for page requests', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const first: FeedbackRecord = { | |
| targetType: 'page_request', | |
| targetId: 'web-feedback-request', | |
| value: 'usable', | |
| note: 'first mark', | |
| source: 'webui', | |
| updatedAt: '2026-05-12T00:00:00.000Z' | |
| }; | |
| const second: FeedbackRecord = { | |
| ...first, | |
| value: 'needs_revision', | |
| note: 'needs copy changes', | |
| updatedAt: '2026-05-12T00:01:00.000Z' | |
| }; | |
| await store.upsertFeedbackBatch([first, second, { ...first, note: 'stale retry' }]); | |
| assert.deepEqual(await store.readFeedback('page_request', 'web-feedback-request'), second); | |
| assert.deepEqual( | |
| await store.listFeedbackByTargets([{ targetType: 'page_request', targetId: 'web-feedback-request' }]), | |
| [second] | |
| ); | |
| assert.equal( | |
| await store.deleteFeedbackByTargets([{ targetType: 'page_request', targetId: 'web-feedback-request' }], { | |
| deletedAt: '2026-05-12T00:00:30.000Z' | |
| }), | |
| 0 | |
| ); | |
| assert.deepEqual(await store.readFeedback('page_request', 'web-feedback-request'), second); | |
| assert.equal( | |
| await store.deleteFeedbackByTargets([{ targetType: 'page_request', targetId: 'web-feedback-request' }]), | |
| 1 | |
| ); | |
| assert.equal(await store.readFeedback('page_request', 'web-feedback-request'), undefined); | |
| }); | |
| it('purges expired terminal requests and their artifact files', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const tempDir = path.join(process.cwd(), 'generated-images', '.memory-purge-test', crypto.randomUUID()); | |
| const artifactPath = path.join(tempDir, 'artifact.png'); | |
| await mkdir(tempDir, { recursive: true }); | |
| await writeFile(artifactPath, 'image'); | |
| const requestJson = { prompt: 'memory purge' }; | |
| const requestHash = hashAgentPayload(requestJson); | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-purge', | |
| requestHash, | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact = buildArtifact({ | |
| id: 'artifact-memory-purge', | |
| requestId: begin.record.requestId, | |
| filepath: artifactPath | |
| }); | |
| const response: AgentImageResponse = { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-memory-purge', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }; | |
| try { | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response, | |
| artifacts: [artifact], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| const purged = await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')); | |
| assert.equal(purged, 1); | |
| assert.equal(await store.getArtifact('artifact-memory-purge'), undefined); | |
| await assert.rejects(() => access(artifactPath)); | |
| } finally { | |
| await rm(tempDir, { recursive: true, force: true }); | |
| } | |
| }); | |
| it('purges feedback rows for expired agent requests and artifacts', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory feedback purge' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-feedback-purge', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 1, | |
| now: new Date('2026-05-12T00:00:00.000Z') | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact = buildArtifact({ id: 'artifact-memory-feedback-purge', requestId: begin.record.requestId }); | |
| await store.completeRequest({ | |
| requestId: begin.record.requestId, | |
| response: { | |
| request_id: begin.record.requestId, | |
| idempotency_key: 'idem-memory-feedback-purge', | |
| cached: false, | |
| images: [], | |
| created_at: '2026-05-12T00:00:00.500Z' | |
| }, | |
| artifacts: [artifact], | |
| now: new Date('2026-05-12T00:00:00.500Z') | |
| }); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_request', | |
| targetId: begin.record.requestId, | |
| value: 'usable', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.600Z' | |
| }); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_artifact', | |
| targetId: artifact.id, | |
| value: 'needs_revision', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.700Z' | |
| }); | |
| assert.equal(await store.purgeExpiredRequests(new Date('2026-05-12T00:00:02.000Z')), 1); | |
| assert.equal(await store.readFeedback('agent_request', begin.record.requestId), undefined); | |
| assert.equal(await store.readFeedback('agent_artifact', artifact.id), undefined); | |
| }); | |
| it('deletes artifact feedback when deleting artifact metadata', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const requestJson = { prompt: 'memory artifact feedback delete' }; | |
| const begin = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-artifact-feedback-delete', | |
| requestHash: hashAgentPayload(requestJson), | |
| mode: 'generate', | |
| requestJson, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(begin.type, 'acquired'); | |
| if (begin.type !== 'acquired') throw new Error('expected acquired'); | |
| const artifact = buildArtifact({ id: 'artifact-memory-feedback-delete', requestId: begin.record.requestId }); | |
| await store.saveArtifacts([artifact]); | |
| await store.upsertFeedback({ | |
| targetType: 'agent_artifact', | |
| targetId: artifact.id, | |
| value: 'usable', | |
| source: 'agent', | |
| updatedAt: '2026-05-12T00:00:00.000Z' | |
| }); | |
| assert.equal(await store.deleteArtifact(artifact.id), true); | |
| assert.equal(await store.readFeedback('agent_artifact', artifact.id), undefined); | |
| }); | |
| it('does not replace a different artifact row when filename collides', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const beginA = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-collision-a', | |
| requestHash: hashAgentPayload({ prompt: 'collision a' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'collision a' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| const beginB = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-collision-b', | |
| requestHash: hashAgentPayload({ prompt: 'collision b' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'collision b' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(beginA.type, 'acquired'); | |
| assert.equal(beginB.type, 'acquired'); | |
| if (beginA.type !== 'acquired' || beginB.type !== 'acquired') throw new Error('expected acquired'); | |
| const first = buildArtifact({ | |
| id: 'artifact-memory-a', | |
| requestId: beginA.record.requestId, | |
| filename: 'shared.png' | |
| }); | |
| const second = buildArtifact({ | |
| id: 'artifact-memory-b', | |
| requestId: beginB.record.requestId, | |
| filename: 'shared.png' | |
| }); | |
| await store.saveArtifacts([first]); | |
| await assert.rejects(() => store.saveArtifacts([second]), /UNIQUE/); | |
| assert.deepEqual(await store.getArtifact('artifact-memory-a'), first); | |
| assert.equal(await store.getArtifact('artifact-memory-b'), undefined); | |
| }); | |
| it('rejects attempts to rewrite an existing artifact id with different metadata', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| const beginA = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-artifact-a', | |
| requestHash: hashAgentPayload({ prompt: 'artifact a' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'artifact a' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| const beginB = await store.beginRequest({ | |
| idempotencyKey: 'idem-memory-artifact-b', | |
| requestHash: hashAgentPayload({ prompt: 'artifact b' }), | |
| mode: 'generate', | |
| requestJson: { prompt: 'artifact b' }, | |
| leaseMs: 1000, | |
| ttlSeconds: 60 | |
| }); | |
| assert.equal(beginA.type, 'acquired'); | |
| assert.equal(beginB.type, 'acquired'); | |
| if (beginA.type !== 'acquired' || beginB.type !== 'acquired') throw new Error('expected acquired'); | |
| const first = buildArtifact({ | |
| id: 'artifact-memory-stable', | |
| requestId: beginA.record.requestId, | |
| filename: 'stable-a.png' | |
| }); | |
| const rewritten = buildArtifact({ | |
| id: 'artifact-memory-stable', | |
| requestId: beginB.record.requestId, | |
| filename: 'stable-b.png' | |
| }); | |
| await store.saveArtifacts([first]); | |
| await assert.rejects(() => store.saveArtifacts([rewritten]), /artifact metadata conflict/); | |
| assert.deepEqual(await store.getArtifact('artifact-memory-stable'), first); | |
| }); | |
| it('stores and reads image share metadata', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| await store.createImageShareRecord({ | |
| token: 'c'.repeat(24), | |
| sourceFilename: 'source.png', | |
| contentFilename: `${'c'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: true, | |
| expiresAt: '2026-05-14T09:00:00.000Z', | |
| accessCodeSalt: 'salt', | |
| accessCodeHash: 'hash' | |
| }); | |
| const record = await store.readImageShareRecord('c'.repeat(24)); | |
| assert.ok(record); | |
| assert.equal(record.sourceFilename, 'source.png'); | |
| assert.equal(record.accessCodeRequired, true); | |
| assert.equal(record.expiresAt, '2026-05-14T09:00:00.000Z'); | |
| }); | |
| it('rejects inconsistent image share access code metadata', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| await assert.rejects( | |
| () => | |
| store.createImageShareRecord({ | |
| token: 'f'.repeat(24), | |
| sourceFilename: 'protected.png', | |
| contentFilename: `${'f'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: true | |
| }), | |
| /CHECK/ | |
| ); | |
| await assert.rejects( | |
| () => | |
| store.createImageShareRecord({ | |
| token: '0'.repeat(24), | |
| sourceFilename: 'public.png', | |
| contentFilename: `${'0'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: false, | |
| accessCodeSalt: 'salt', | |
| accessCodeHash: 'hash' | |
| }), | |
| /CHECK/ | |
| ); | |
| }); | |
| it('deletes expired image share records and lists active share records', async () => { | |
| const store = new MemoryAgentStateStore(); | |
| await store.init(); | |
| await store.createImageShareRecord({ | |
| token: 'd'.repeat(24), | |
| sourceFilename: 'expired.png', | |
| contentFilename: `${'d'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: false, | |
| expiresAt: '2026-05-14T09:00:00.000Z' | |
| }); | |
| await store.createImageShareRecord({ | |
| token: 'e'.repeat(24), | |
| sourceFilename: 'active.png', | |
| contentFilename: `${'e'.repeat(24)}.png`, | |
| mimeType: 'image/png', | |
| sizeBytes: 12, | |
| createdAt: '2026-05-14T08:00:00.000Z', | |
| accessCodeRequired: false, | |
| expiresAt: '2026-05-14T10:00:00.000Z' | |
| }); | |
| const expired = await store.deleteExpiredImageShareRecords('2026-05-14T09:00:01.000Z'); | |
| const active = await store.listImageShareRecords(); | |
| assert.deepEqual( | |
| expired.map((record) => record.token), | |
| ['d'.repeat(24)] | |
| ); | |
| assert.deepEqual( | |
| active.map((record) => record.token), | |
| ['e'.repeat(24)] | |
| ); | |
| }); | |
| }); | |
| function buildArtifact(input: { | |
| id: string; | |
| requestId: string; | |
| filename?: string; | |
| filepath?: string; | |
| }): AgentArtifactRecord { | |
| return { | |
| id: input.id, | |
| requestId: input.requestId, | |
| filename: input.filename || `${input.id}.png`, | |
| filepath: input.filepath || path.join(process.cwd(), 'generated-images', `${input.id}.png`), | |
| contentUrl: `/api/agent/artifacts/${input.id}/content`, | |
| metadataUrl: `/api/agent/artifacts/${input.id}`, | |
| outputFormat: 'png', | |
| mimeType: 'image/png', | |
| sizeBytes: 1, | |
| width: 1, | |
| height: 1, | |
| model: 'gpt-image-2', | |
| promptHash: 'hash', | |
| createdAt: '2026-05-12T00:00:00.500Z' | |
| }; | |
| } | |