Codex Task List for ModelFaucet MVP
This file is written as a direct implementation guide for Codex or another coding agent.
Global rules:
- Work in small commits.
- Add tests for every feature.
- Do not put provider API keys in client-side code.
- Do not log raw secrets.
- Do not make cloud services call localhost or private LAN URLs.
- Use TypeScript strict mode.
- Prefer boring, maintainable code over clever abstractions.Prompt 0 - Bootstrap the repository
Implement the monorepo skeleton.
Create:
package.json
pnpm-workspace.yaml
turbo.json
tsconfig.base.json
.env.example
docker-compose.yml
apps/api
apps/gateway
apps/dashboard
packages/shared
packages/sdk-js
packages/react
services/rating-worker
services/settlement-worker
services/local-bridge
infra/db
infra/docker
examples/crm-demoAcceptance criteria:
pnpm install works
pnpm lint works
pnpm typecheck works
pnpm test works, even if no-op tests initiallyPrompt 1 - Add database schema and seed
Implement infra/db/schema.sql and infra/db/seed.sql.
Tables:
developers
apps
app_features
end_users
provider_credentials
virtual_sessions
usage_events
wallets
ledger_entries
payouts
audit_logsSeed:
Demo Developer
CRM Demo app
public_app_id = app_pub_demo
customer_reply feature
end user test wallet = 10 USDAcceptance criteria:
docker compose up -d postgres
psql can apply schema.sql
psql can apply seed.sql
all tables exist
seed rows existPrompt 2 - Build shared types and schemas
Implement packages/shared.
Files:
src/types.ts
src/schemas.ts
src/errors.ts
src/pricing.tsUse Zod schemas for:
CreateSessionRequest
CreateSessionResponse
ChatCompletionRequest
AddProviderKeyRequest
UsageEvent
RatedUsage
RevenueRuleAcceptance criteria:
pnpm --filter @modelfaucet/shared test
all schema tests passPrompt 3 - Build Control API health and session endpoint
Implement apps/api with Fastify.
Endpoints:
GET /health
POST /v1/sessionsBehavior:
POST /v1/sessions accepts public_app_id and external_user_id
hash external_user_id
upsert end_user
create virtual_sessions row with hashed token
return mf_sess_xxx tokenAcceptance criteria:
curl GET /health returns ok
curl POST /v1/sessions returns session_token
virtual_sessions row exists with token_hash, not raw token
end_users row stores hash, not raw external_user_idPrompt 4 - Build Gateway mock route
Implement apps/gateway with Fastify.
Endpoint:
POST /v1/chat/completionsInitial behavior:
validate session token by calling database or API service
return a mock OpenAI-compatible response
write a mock usage_event
call rating engine
write ledger entriesAcceptance criteria:
valid session can call gateway
expired or invalid session is rejected
mock response matches OpenAI-like shape
usage_events has one row
ledger_entries has debit/credit rowsPrompt 5 - Implement Rating Engine
Implement services/rating-worker/src/rateUsage.ts.
Cases:
platform
developer_key
byok
localAcceptance criteria:
unit tests for platform markup and channel share
unit tests for BYOK no upstream platform cost
unit tests for local no upstream platform cost
negative tokens rejected
unknown route rejectedPrompt 6 - Implement Ledger Service
Implement transactional ledger logic.
Function:
recordRatedUsage(ratedUsage): Promise<void>Requirements:
Use database transaction.
request_id idempotency.
Use decimal math.
Never physically delete ledger entries.Acceptance criteria:
same request_id twice does not double charge
wallet balances update correctly
rollback on failurePrompt 7 - Integrate LiteLLM Proxy
Update gateway route to call LiteLLM.
Config:
LITELLM_BASE_URL=http://localhost:4000
LITELLM_MASTER_KEY=sk-litellm-dev-master-keyBehavior:
For model auto:customer_reply, route to LiteLLM model auto-text.
Proxy request and response.
Capture usage from provider response if present.
Fallback to tokenizer estimate if usage missing.Acceptance criteria:
with LiteLLM running and provider key configured, real LLM response returns
usage_events captures token counts
ledger_entries captures rated usagePrompt 8 - Build SDK JS
Implement packages/sdk-js.
API:
const faucet = createFaucet({ publicAppId, user, baseUrl, gatewayBaseUrl });
await faucet.createSession();
await faucet.chat({ feature, input });Acceptance criteria:
SDK can create session
SDK can call gateway
SDK refreshes session when expired
SDK does not accept or expose provider API keys in default modePrompt 9 - Build React components
Implement:
FaucetProvider
FaucetChatPhase 1 only.
Acceptance criteria:
CRM demo can render FaucetChat
User can send prompt
Response appears
Errors are displayedPrompt 10 - Build CRM demo
Create examples/crm-demo.
UI:
textarea with customer ticket
button: Generate Reply
reply output
usage metadata displayAcceptance criteria:
pnpm --filter crm-demo dev works
Demo calls SDK
Usage appears in dashboard or API responsePrompt 11 - Build Dashboard MVP
Implement pages:
/dashboard
/apps/app_pub_demo/usage
/revenueAcceptance criteria:
Dashboard shows total calls
total input/output tokens
total retail price
total developer revenue
usage table lists request_id and feature_keyPrompt 12 - BYOK storage
Implement endpoints:
POST /v1/user/provider-keys
GET /v1/user/provider-keys
DELETE /v1/user/provider-keys/:idRequirements:
encrypt key
mask key
never return raw key
audit log
basic provider validationAcceptance criteria:
raw key is not in API response
raw key is not in logs
list shows masked key
delete disables keyPrompt 13 - BYOK routing
Gateway route selection should support BYOK.
Behavior:
If user has active BYOK and feature/user policy says byok_first, use BYOK route.
Do not apply hidden token markup.
Record route_mode = byok.
Set upstream_cost_usd = 0 for ModelFaucet.Acceptance criteria:
BYOK route works
usage_events.route_mode = byok
platform upstream cost is zeroPrompt 14 - Local Bridge
Implement Go service:
modelfaucet-bridge start --port 8787
GET /health
GET /models
POST /v1/chat/completions
POST /usage/reportAcceptance criteria:
Bridge starts on 127.0.0.1:8787
Bridge can proxy to Ollama OpenAI-compatible endpoint
Bridge can report usage metadata
Cloud API never fetches local/LAN endpoint directlyPrompt 15 - SDK local detection
Add SDK local support:
await faucet.local.detectBridge();
await faucet.local.listModels();
await faucet.chat({ routeMode: "local", ... });Acceptance criteria:
SDK detects bridge
SDK can call local bridge
usage route_mode = localPrompt 16 - Developer provider keys
Add developer key management in dashboard/API.
Endpoints:
POST /v1/developer/provider-keys
GET /v1/developer/provider-keys
DELETE /v1/developer/provider-keys/:idAcceptance criteria:
Developer can add OpenRouter/OpenAI key
Gateway can route developer_key before platform_pool
Budget limits are enforcedPrompt 17 - Credits and payment mock
Implement internal credits without Stripe first.
Endpoints:
POST /v1/admin/wallets/:id/credit-test-balance
GET /v1/user/walletAcceptance criteria:
User with $0 balance cannot use platform route
Admin can credit balance
User can use platform route after creditPrompt 18 - Stripe test mode
Add Stripe test mode top-up.
Flow:
create checkout session
webhook confirms payment
credit end_user_walletAcceptance criteria:
Stripe test card adds credits
wallet balance updates once
webhook idempotency worksPrompt 19 - Payout mock
Implement payout simulation.
Behavior:
Developer wallet accumulates revenue.
If balance >= threshold, create payout pending.
Admin marks payout paid in dev mode.Acceptance criteria:
Developer revenue becomes pending payout
Paid payout reduces available balance
Audit log createdPrompt 20 - Documentation and launch prep
Add:
CONTRIBUTING.md
SECURITY.md
LICENSE
CHANGELOG.md
.github/workflows/ci.ymlAcceptance criteria:
Fresh clone can run README quickstart
CI passes
No provider keys in repo
No raw secrets in docs examples except placeholders