[ai] fix stub endpoint

This commit is contained in:
Devin Zuczek
2026-08-20 18:27:42 -04:00
parent 038e3b3c50
commit e47ce58db3
3 changed files with 41 additions and 30 deletions
+20 -16
View File
@@ -235,28 +235,32 @@ const app = new Hono<App>()
} }
) )
// Whether the caller may use Maker AI at all. Always false: no model runs behind this // Whether the caller may use Maker AI at all. Granted, like the Roomie budget reads and
// worker, so the honest answer is that the feature isn't available — and false is what // unlike the Game AI checks: this is a gate, not a model call, and refusing it hides the
// leaves the creation UI in its normal state rather than offering a tool that can't // feature outright. (The balances below are still zeroed: the client reads its usage
// work. (The balances below are still served: the client reads its usage meter // meter separately, and a server that bills nothing has spent nothing.)
// separately, and a server that bills nothing has spent nothing.)
// //
// The body is a BARE JSON `false` — not an envelope, unlike the Game AI refusal and the // The envelope is its own shape again — PascalCase `Success`/`Error` beside a snake_case
// Roomie access check on either side of it. `econ`'s // `error_id`, which is neither the Game AI refusal's all-lowercase body nor the Roomie
// `/api/makerai/checkfreetrialeligibility` answers the same bare shape. // check's `{ success, error_id, error, value }`. Reproduced as the reference sends it;
// the mixed casing is not a typo to tidy up.
.get( .get(
'/makerai/user/access', '/makerai/user/access',
describeRoute({ describeRoute({
tags: ['Maker AI'], tags: ['Maker AI'],
summary: 'May the caller use Maker AI?', summary: 'May the caller use Maker AI?',
description: [ description: [
'Asked before the client offers Maker AI. Always `false` — no model runs behind this', 'Asked before the client offers Maker AI. Always granted — the gate is about',
'worker. The body is a bare JSON boolean, not the `{ success, error, value }` envelope', 'entitlement, not capacity, and nothing here meters what Maker AI would cost.',
'the neighbouring checks answer with.',
'', '',
'`roomInstanceSpecificCheck` (the client sends .NETs `False`) is accepted and ignored:', 'The envelope carries PascalCase `Success`/`Error` next to a snake_case `error_id`,',
'it asks whether the check is about the instance the player is standing in rather than', 'which matches neither neighbour on this worker. That mix is what the reference sends;',
'the account, and the answer is the same either way. The token is still validated first.', 'it is not an inconsistency to clean up.',
'',
'`roomInstanceSpecificCheck` (the client sends .NETs `True`/`False`) is accepted and',
'ignored: it asks whether the check is about the instance the player is standing in',
'rather than the account, and the answer is the same either way. The token is still',
'validated first.',
].join(' '), ].join(' '),
security: AUTHED, security: AUTHED,
parameters: [ parameters: [
@@ -266,7 +270,7 @@ const app = new Hono<App>()
), ),
], ],
responses: { responses: {
200: json(MakerAiAccessResponse, 'Always `false`'), 200: json(MakerAiAccessResponse, 'Always granted'),
401: UNAUTHORIZED_RESPONSE, 401: UNAUTHORIZED_RESPONSE,
}, },
}), }),
@@ -274,7 +278,7 @@ const app = new Hono<App>()
const id = await authedId(c) const id = await authedId(c)
if (id === null) return unauthorized(c) if (id === null) return unauthorized(c)
return c.json(false) return c.json({ Success: true, Error: null, error_id: null })
} }
) )
+10 -5
View File
@@ -113,12 +113,17 @@ export const GameAiSpendSummaryDenied = GameAiAccessDenied.extend({
}) })
/** /**
* `GET /makerai/user/access` — a BARE JSON boolean, not an envelope and not a `{ value }` * `GET /makerai/user/access` — always granted, in an envelope that belongs to this endpoint
* wrapper. The whole body is the answer. * alone: PascalCase `Success`/`Error` beside a snake_case `error_id`, and no `value` slot.
* It is neither the Game AI refusal's all-lowercase body nor the Roomie check's
* `{ success, error_id, error, value }`. Reproduced as the reference sends it — the casing
* mix is not a typo to normalise.
*/ */
export const MakerAiAccessResponse = z export const MakerAiAccessResponse = z.object({
.boolean() Success: z.boolean().describe('Whether the caller may use Maker AI. Always true'),
.describe('Whether the caller may use Maker AI; always false — no model runs here') Error: z.null().describe('The failure message. Null — the check always passes'),
error_id: z.null().describe('The failure code. Null — the check always passes'),
})
/** /**
* Maker AI's dollar balances. A FLAT body — no `{ success, error, value }` envelope — and * Maker AI's dollar balances. A FLAT body — no `{ success, error, value }` envelope — and
+11 -9
View File
@@ -175,29 +175,31 @@ describe('GET /roomieai/user/facts', () => {
}) })
describe('GET /makerai/user/access', () => { describe('GET /makerai/user/access', () => {
// Always false — nothing here runs a model. The body is the boolean itself, not an // Granted, and pinned whole: the casing is mixed on purpose (PascalCase `Success`/`Error`
// envelope, matching econ's `/api/makerai/checkfreetrialeligibility`. // beside a snake_case `error_id`) and matches neither neighbour on this worker, so a
it('refuses access with a bare false', async () => { // "consistency" edit has to fail here rather than on the client.
const res = await SELF.fetch(`${ORIGIN}/makerai/user/access?roomInstanceSpecificCheck=False`, { it('grants access', async () => {
const res = await SELF.fetch(`${ORIGIN}/makerai/user/access?roomInstanceSpecificCheck=True`, {
headers: await bearer(), headers: await bearer(),
}) })
expect(res.status).toBe(200) expect(res.status).toBe(200)
expect(res.headers.get('content-type')).toContain('application/json') expect(res.headers.get('content-type')).toContain('application/json')
expect(await res.text()).toBe('false') expect(await res.json()).toEqual({ Success: true, Error: null, error_id: null })
}) })
it('answers the same without the query param', async () => { it('answers the same without the query param', async () => {
// `roomInstanceSpecificCheck` is ignored, so its presence, absence and value change // `roomInstanceSpecificCheck` is ignored, so its presence, absence and value change
// nothing. // nothing.
const granted = { Success: true, Error: null, error_id: null }
const res = await SELF.fetch(`${ORIGIN}/makerai/user/access`, { headers: await bearer() }) const res = await SELF.fetch(`${ORIGIN}/makerai/user/access`, { headers: await bearer() })
expect(await res.text()).toBe('false') expect(await res.json()).toEqual(granted)
const trueCheck = await SELF.fetch( const falseCheck = await SELF.fetch(
`${ORIGIN}/makerai/user/access?roomInstanceSpecificCheck=True`, `${ORIGIN}/makerai/user/access?roomInstanceSpecificCheck=False`,
{ {
headers: await bearer(), headers: await bearer(),
} }
) )
expect(await trueCheck.text()).toBe('false') expect(await falseCheck.json()).toEqual(granted)
}) })
it('401s without a bearer token', async () => { it('401s without a bearer token', async () => {