Initial import

This commit is contained in:
sirily
2026-03-10 14:03:52 +03:00
commit 6c0ca4e28b
102 changed files with 6598 additions and 0 deletions

17
apps/worker/AGENTS.md Normal file
View File

@@ -0,0 +1,17 @@
# AGENTS.md
## Scope
Applies within `apps/worker`.
## Responsibilities
- generation job execution
- payment reconciliation
- media cleanup
- provider-key balance polling
- provider-key recovery checks
- alert and reminder jobs
## Rules
- Persist every provider-key attempt.
- Consume quota only after a request succeeds.
- Keep retries idempotent and auditable.

3
apps/worker/README.md Normal file
View File

@@ -0,0 +1,3 @@
# apps/worker
Planned worker runtime for queued and scheduled jobs.

16
apps/worker/package.json Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "@nproxy/worker",
"version": "0.1.0",
"private": true,
"type": "module",
"scripts": {
"build": "tsc -p tsconfig.json",
"start": "node dist/main.js"
},
"dependencies": {
"@nproxy/config": "workspace:*",
"@nproxy/db": "workspace:*",
"@nproxy/domain": "workspace:*",
"@nproxy/providers": "workspace:*"
}
}

0
apps/worker/src/.gitkeep Normal file
View File

150
apps/worker/src/main.ts Normal file
View File

@@ -0,0 +1,150 @@
import { loadConfig } from "@nproxy/config";
import { createPrismaWorkerStore, prisma } from "@nproxy/db";
import { createNanoBananaSimulatedAdapter } from "@nproxy/providers";
const config = loadConfig();
const intervalMs = config.keyPool.balancePollSeconds * 1000;
const workerStore = createPrismaWorkerStore(prisma, {
cooldownMinutes: config.keyPool.cooldownMinutes,
failuresBeforeManualReview: config.keyPool.failuresBeforeManualReview,
});
const nanoBananaAdapter = createNanoBananaSimulatedAdapter();
let isTickRunning = false;
console.log(
JSON.stringify({
service: "worker",
balancePollSeconds: config.keyPool.balancePollSeconds,
providerModel: config.provider.nanoBananaDefaultModel,
}),
);
setInterval(() => {
void runTick();
}, intervalMs);
void runTick();
process.once("SIGTERM", async () => {
await prisma.$disconnect();
process.exit(0);
});
process.once("SIGINT", async () => {
await prisma.$disconnect();
process.exit(0);
});
async function runTick(): Promise<void> {
if (isTickRunning) {
console.log("worker tick skipped because previous tick is still running");
return;
}
isTickRunning = true;
try {
const recovery = await workerStore.recoverCooldownProviderKeys();
if (recovery.recoveredCount > 0) {
console.log(
JSON.stringify({
service: "worker",
event: "cooldown_keys_recovered",
recoveredCount: recovery.recoveredCount,
}),
);
}
const job = await workerStore.claimNextQueuedGenerationJob();
if (!job) {
console.log(`worker heartbeat interval=${intervalMs} no_queued_jobs=true`);
return;
}
const result = await workerStore.processClaimedGenerationJob(
job,
async (request, providerKey) => {
if (providerKey.providerCode !== config.provider.nanoBananaDefaultModel) {
return {
ok: false as const,
usedProxy: false,
directFallbackUsed: false,
failureKind: "unknown" as const,
providerErrorCode: "unsupported_provider_model",
providerErrorText: `Unsupported provider model: ${providerKey.providerCode}`,
};
}
if (providerKey.proxyBaseUrl) {
const proxyResult = await nanoBananaAdapter.executeGeneration({
request,
providerKey: {
id: providerKey.id,
providerCode: providerKey.providerCode,
label: providerKey.label,
apiKeyLastFour: providerKey.apiKeyLastFour,
},
route: {
kind: "proxy",
proxyBaseUrl: providerKey.proxyBaseUrl,
},
});
if (!proxyResult.ok && proxyResult.failureKind === "transport") {
const directResult = await nanoBananaAdapter.executeGeneration({
request,
providerKey: {
id: providerKey.id,
providerCode: providerKey.providerCode,
label: providerKey.label,
apiKeyLastFour: providerKey.apiKeyLastFour,
},
route: {
kind: "direct",
},
});
return {
...directResult,
usedProxy: true,
directFallbackUsed: true,
};
}
return {
...proxyResult,
usedProxy: true,
directFallbackUsed: false,
};
}
const directResult = await nanoBananaAdapter.executeGeneration({
request,
providerKey: {
id: providerKey.id,
providerCode: providerKey.providerCode,
label: providerKey.label,
apiKeyLastFour: providerKey.apiKeyLastFour,
},
route: {
kind: "direct",
},
});
return {
...directResult,
usedProxy: false,
directFallbackUsed: false,
};
},
);
console.log(JSON.stringify({ service: "worker", event: "job_processed", ...result }));
} catch (error) {
console.error("worker tick failed", error);
} finally {
isTickRunning = false;
}
}

12
apps/worker/tsconfig.json Normal file
View File

@@ -0,0 +1,12 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"types": ["node"]
},
"include": ["src/**/*.ts"]
}