diff --git a/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts b/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts index c976adfbe..05fff2fb9 100644 --- a/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts +++ b/apps/api/src/handlers/inference/__tests__/inference-gateway.test.ts @@ -10,6 +10,7 @@ const { mockGetGitHubCopilotAccessToken, mockGetFreshXaiAccessToken, mockRecordLlmUsage, + mockCaptureActivationTrialInferenceUsed, } = vi.hoisted(() => ({ mockFindTaskRun: vi.fn(), mockResolveModelProviderEnvValue: vi.fn(), @@ -17,6 +18,7 @@ const { mockGetGitHubCopilotAccessToken: vi.fn(), mockGetFreshXaiAccessToken: vi.fn(), mockRecordLlmUsage: vi.fn(), + mockCaptureActivationTrialInferenceUsed: vi.fn(), })); vi.mock('@roomote/db/server', () => ({ @@ -37,6 +39,10 @@ vi.mock('@roomote/sdk/server', () => ({ recordLlmUsage: mockRecordLlmUsage, })); +vi.mock('@roomote/telemetry/server', () => ({ + captureActivationTrialInferenceUsed: mockCaptureActivationTrialInferenceUsed, +})); + import { inference } from '../index'; import { resetRoomoteInferenceKeyCache } from '../registry'; @@ -284,6 +290,40 @@ describe('inference gateway', () => { expect(JSON.parse(String(init.body))).toMatchObject({ model: 'openai/gpt-5.6-luna', }); + expect(mockCaptureActivationTrialInferenceUsed).toHaveBeenCalledOnce(); + }); + + it('does not record trial inference usage for an upstream error', async () => { + stubUpstreamFetch( + new Response(JSON.stringify({ error: { message: 'provider error' } }), { + status: 500, + headers: { 'content-type': 'application/json' }, + }), + ); + + const response = await appRequest( + createApp(createRunToken()), + '/api/inference/roomote/v1/chat/completions', + { model: 'roomote/openai/gpt-5.6-luna', messages: [] }, + ); + + expect(response.status).toBe(500); + expect(mockCaptureActivationTrialInferenceUsed).not.toHaveBeenCalled(); + }); + + it('does not record trial inference usage for model discovery', async () => { + stubUpstreamFetch(); + + const response = await createApp(createRunToken()).request( + '/api/inference/roomote/v1/models', + { + method: 'GET', + headers: { authorization: 'Bearer run-token-value' }, + }, + ); + + expect(response.status).toBe(200); + expect(mockCaptureActivationTrialInferenceUsed).not.toHaveBeenCalled(); }); it('returns an actionable response when managed Roomote inference is unavailable', async () => { @@ -1362,9 +1402,14 @@ describe('inference gateway', () => { .mockRejectedValue(new Error('connect ECONNREFUSED')); vi.stubGlobal('fetch', fetchMock); - const response = await postMessages(createApp(createRunToken())); + const response = await appRequest( + createApp(createRunToken()), + '/api/inference/roomote/v1/chat/completions', + { model: 'roomote/openai/gpt-5.6-luna', messages: [] }, + ); expect(response.status).toBe(502); + expect(mockCaptureActivationTrialInferenceUsed).not.toHaveBeenCalled(); }); it('rejects unsupported methods', async () => { diff --git a/apps/api/src/handlers/inference/index.ts b/apps/api/src/handlers/inference/index.ts index ec89181f2..bc28ed260 100644 --- a/apps/api/src/handlers/inference/index.ts +++ b/apps/api/src/handlers/inference/index.ts @@ -1,4 +1,5 @@ import { Hono } from 'hono'; +import { captureActivationTrialInferenceUsed } from '@roomote/telemetry/server'; import { formatSingleLineLog, @@ -476,6 +477,14 @@ inference.on(['POST', 'GET'], '/:provider/*', async (c) => { } } + if ( + providerId === ROOMOTE_INFERENCE_PROVIDER_ID && + method === 'POST' && + upstreamResponse.ok + ) { + void captureActivationTrialInferenceUsed(); + } + return new Response( createLoggedProxyResponseBody({ body: upstreamResponse.body, diff --git a/packages/telemetry/src/server/index.ts b/packages/telemetry/src/server/index.ts index d8ddd58d7..91ca52f9c 100644 --- a/packages/telemetry/src/server/index.ts +++ b/packages/telemetry/src/server/index.ts @@ -367,6 +367,10 @@ export async function captureActivationTaskCreated( ); } +export async function captureActivationTrialInferenceUsed(): Promise { + return captureInstanceEvent('activation_trial_inference_used'); +} + export async function captureActivationPrMerged(properties: { provider: string; workflow: string;