Initial import
This commit is contained in:
17
apps/worker/AGENTS.md
Normal file
17
apps/worker/AGENTS.md
Normal 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
3
apps/worker/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# apps/worker
|
||||
|
||||
Planned worker runtime for queued and scheduled jobs.
|
||||
16
apps/worker/package.json
Normal file
16
apps/worker/package.json
Normal 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
0
apps/worker/src/.gitkeep
Normal file
150
apps/worker/src/main.ts
Normal file
150
apps/worker/src/main.ts
Normal 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
12
apps/worker/tsconfig.json
Normal 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"]
|
||||
}
|
||||
Reference in New Issue
Block a user