@cartanova/qgrid-cli 1.5.5 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/bundle/dist/application/qgrid/oauth.js +1 -1
  2. package/bundle/dist/application/qgrid/qgrid.dispatcher.js +166 -0
  3. package/bundle/dist/application/qgrid/qgrid.frame.js +28 -36
  4. package/bundle/dist/application/qgrid/qgrid.types.js +5 -16
  5. package/bundle/dist/application/queries.generated.js +1 -1
  6. package/bundle/dist/application/sonamu.generated.js +1 -1
  7. package/bundle/dist/application/sonamu.generated.sso.js +1 -1
  8. package/bundle/dist/sonamu.config.js +6 -9
  9. package/bundle/src/application/qgrid/oauth.ts +1 -1
  10. package/bundle/src/application/qgrid/qgrid.dispatcher.ts +184 -0
  11. package/bundle/src/application/qgrid/qgrid.frame.ts +28 -50
  12. package/bundle/src/application/qgrid/qgrid.types.ts +2 -14
  13. package/bundle/src/application/queries.generated.ts +1 -0
  14. package/bundle/src/application/sonamu.generated.http +2 -1
  15. package/bundle/src/application/sonamu.generated.sso.ts +6 -5
  16. package/bundle/src/application/sonamu.generated.ts +1 -0
  17. package/bundle/src/sonamu.config.ts +4 -7
  18. package/bundle/web-dist/client/assets/index-BRZCFaFz.css +1 -0
  19. package/bundle/web-dist/client/assets/index-D8eEjsZj.js +78 -0
  20. package/bundle/web-dist/client/assets/{logs-CQDBOCud.js → logs-CKnJzuH6.js} +1 -1
  21. package/bundle/web-dist/client/assets/{routes-MR1DKizN.js → routes-DQKecVGa.js} +1 -1
  22. package/bundle/web-dist/client/assets/sd.generated-NHYHS_-0.js +47 -0
  23. package/bundle/web-dist/client/assets/services.generated-DDVXMid7.js +1 -0
  24. package/bundle/web-dist/client/assets/{show--z3WxDb0.js → show-D2z1qxJ5.js} +1 -1
  25. package/bundle/web-dist/client/assets/tokens-BaywaQkm.js +1 -0
  26. package/bundle/web-dist/client/assets/vendor-tanstack-DJRQAbrY.js +1 -0
  27. package/bundle/web-dist/client/index.html +4 -4
  28. package/bundle/web-dist/server/assets/{copy-GKsNP-2_.js → copy-BVWDOV2T.js} +1 -1
  29. package/bundle/web-dist/server/assets/{lazyRouteComponent-B78WDGgM.js → lazyRouteComponent-BJmwxFyJ.js} +2 -128
  30. package/bundle/web-dist/server/assets/{logs-BV6gEe15.js → logs-DgIbufNf.js} +4 -4
  31. package/bundle/web-dist/server/assets/{routes-B5FkWLUe.js → routes-x7-hqUm0.js} +3 -3
  32. package/bundle/web-dist/server/assets/{sd.generated-Mnbmi4YR.js → sd.generated-BZPIp4uL.js} +122 -1
  33. package/bundle/web-dist/server/assets/{services.generated-DjdxFvCg.js → services.generated-BA0gbdmd.js} +134 -20
  34. package/bundle/web-dist/server/assets/shim-DGrmZkac.js +129 -0
  35. package/bundle/web-dist/server/assets/{show-BqSxVXai.js → show-Bk3NayMH.js} +4 -4
  36. package/bundle/web-dist/server/assets/{tokens-BGaTdsEb.js → tokens-CVSwt0x0.js} +13 -24
  37. package/bundle/web-dist/server/entry-server.generated.js +19679 -17705
  38. package/package.json +2 -2
  39. package/bundle/dist/application/qgrid/pool.js +0 -128
  40. package/bundle/dist/application/qgrid/worker.js +0 -189
  41. package/bundle/src/application/qgrid/pool.ts +0 -165
  42. package/bundle/src/application/qgrid/worker.ts +0 -240
  43. package/bundle/web-dist/client/assets/index-DLrkHIo2.js +0 -116
  44. package/bundle/web-dist/client/assets/index-kQRqXaWM.css +0 -1
  45. package/bundle/web-dist/client/assets/sd.generated-DXtkTTGn.js +0 -47
  46. package/bundle/web-dist/client/assets/services.generated-C0OpNm5q.js +0 -1
  47. package/bundle/web-dist/client/assets/tokens-Bch6XO9K.js +0 -1
  48. package/bundle/web-dist/client/assets/vendor-tanstack-CUxlo6YN.js +0 -1
  49. /package/bundle/web-dist/server/assets/{cost-D0Ta7MJa.js → cost-DdHTzQDR.js} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cartanova/qgrid-cli",
3
- "version": "1.5.5",
3
+ "version": "1.6.0",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "https://github.com/cartanova-ai/qgrid"
@@ -37,7 +37,7 @@
37
37
  "knex": "^3.1.0",
38
38
  "pg": "^8.16.3",
39
39
  "radashi": "^12.2.0",
40
- "sonamu": "^0.9.2",
40
+ "sonamu": "^0.9.4",
41
41
  "typescript": "5.9.3",
42
42
  "zod": "^4.3.6"
43
43
  },
@@ -1,128 +0,0 @@
1
- import "../../_virtual/_rolldown/runtime.js";
2
- import { QuotaError } from "./qgrid.types.js";
3
- import { Worker } from "./worker.js";
4
- //#region src/application/qgrid/pool.ts
5
- var ClaudePool = class {
6
- workers = /* @__PURE__ */ new Map();
7
- tokenNames = /* @__PURE__ */ new Map();
8
- quotaExhausted = /* @__PURE__ */ new Set();
9
- requestCounts = /* @__PURE__ */ new Map();
10
- lastUsedToken = "";
11
- lastUsedWorkerIndex = 0;
12
- size;
13
- model;
14
- timeout;
15
- cwd;
16
- maxCalls;
17
- constructor(config) {
18
- this.size = config.size ?? 3;
19
- this.model = config.model ?? "sonnet";
20
- this.timeout = config.timeout ?? 3e5;
21
- this.cwd = config.cwd ?? "/tmp/qgrid";
22
- this.maxCalls = config.maxCalls ?? 500;
23
- config.tokens.forEach(({ token, name }) => {
24
- this.createWorkers(token, name);
25
- });
26
- }
27
- getLastUsedWorkerName() {
28
- return `${this.tokenNames.get(this.lastUsedToken) ?? "Unknown"}-${this.lastUsedWorkerIndex}`;
29
- }
30
- getStats() {
31
- return [...this.workers.keys()].map((token) => ({
32
- token,
33
- name: this.tokenNames.get(token) ?? "Unknown",
34
- requests: this.requestCounts.get(token) ?? 0,
35
- active: !this.quotaExhausted.has(token)
36
- }));
37
- }
38
- tokenRrIndex = 0;
39
- workerRrIndexes = /* @__PURE__ */ new Map();
40
- selectWorker() {
41
- const availableTokens = [...this.workers.entries()].filter(([token]) => !this.quotaExhausted.has(token));
42
- if (availableTokens.length === 0) return null;
43
- const tokenDepths = availableTokens.map(([token, workers]) => ({
44
- token,
45
- workers,
46
- totalDepth: workers.reduce((sum, w) => sum + w.getQueueDepth(), 0)
47
- }));
48
- const minTokenDepth = Math.min(...tokenDepths.map((t) => t.totalDepth));
49
- const idleTokens = tokenDepths.filter((t) => t.totalDepth === minTokenDepth);
50
- const selected = idleTokens[this.tokenRrIndex % idleTokens.length];
51
- this.tokenRrIndex++;
52
- const minDepth = Math.min(...selected.workers.map((w) => w.getQueueDepth()));
53
- const idleWorkers = selected.workers.filter((w) => w.getQueueDepth() === minDepth);
54
- const wrIdx = this.workerRrIndexes.get(selected.token) ?? 0;
55
- const picked = idleWorkers[wrIdx % idleWorkers.length];
56
- this.workerRrIndexes.set(selected.token, wrIdx + 1);
57
- return picked;
58
- }
59
- async query(input, timeoutMs) {
60
- const triedTokens = /* @__PURE__ */ new Set();
61
- while (true) {
62
- const worker = this.selectWorker();
63
- if (!worker) throw new QuotaError("All tokens exhausted");
64
- if (triedTokens.has(worker.tokenId)) throw new QuotaError("All tokens exhausted");
65
- const workerName = this.tokenNames.get(worker.tokenId) ?? "Unknown";
66
- const tokenWorkers = this.workers.get(worker.tokenId);
67
- const workerIdx = tokenWorkers ? tokenWorkers.indexOf(worker) + 1 : 0;
68
- console.log(`[qgrid] → ${workerName}-${workerIdx} (queue: ${worker.getQueueDepth()})`);
69
- try {
70
- const result = await worker.query(input, timeoutMs);
71
- this.lastUsedToken = worker.tokenId;
72
- this.lastUsedWorkerIndex = workerIdx;
73
- this.requestCounts.set(worker.tokenId, (this.requestCounts.get(worker.tokenId) ?? 0) + 1);
74
- return result;
75
- } catch (err) {
76
- if (err instanceof QuotaError) {
77
- this.quotaExhausted.add(worker.tokenId);
78
- triedTokens.add(worker.tokenId);
79
- continue;
80
- }
81
- throw err;
82
- }
83
- }
84
- }
85
- kill() {
86
- [...this.workers.values()].flat().forEach((w) => {
87
- w.kill();
88
- });
89
- }
90
- createWorkers(token, name) {
91
- if (this.workers.has(token)) return;
92
- this.tokenNames.set(token, name);
93
- const workerConfig = {
94
- token,
95
- model: this.model,
96
- timeout: this.timeout,
97
- cwd: this.cwd,
98
- maxCalls: this.maxCalls
99
- };
100
- const workers = Array.from({ length: this.size }, () => new Worker(workerConfig));
101
- this.workers.set(token, workers);
102
- this.requestCounts.set(token, 0);
103
- }
104
- destroyWorkers(token) {
105
- const workers = this.workers.get(token);
106
- if (!workers) return;
107
- workers.forEach((w) => {
108
- w.kill();
109
- });
110
- this.workers.delete(token);
111
- this.tokenNames.delete(token);
112
- this.quotaExhausted.delete(token);
113
- this.requestCounts.delete(token);
114
- }
115
- };
116
- let pool = null;
117
- function initPool(tokens) {
118
- pool = new ClaudePool({ tokens });
119
- return pool;
120
- }
121
- function getPool() {
122
- if (!pool) throw new Error("Pool not initialized. Call initPool() first.");
123
- return pool;
124
- }
125
- //#endregion
126
- export { ClaudePool, getPool, initPool };
127
-
128
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicG9vbC5qcyIsIm5hbWVzIjpbXSwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvYXBwbGljYXRpb24vcWdyaWQvcG9vbC50cyJdLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIENsYXVkZVBvb2wg4oCUIOupgO2LsCDthqDtgbAg7ZSE66Gc7IS47IqkIO2SgC5cbiAqXG4gKiAyLWxheWVyIOudvOyasO2MhTog7Yag7YGwIOyEoO2DnSAobGVhc3QtdG90YWwtZGVwdGggKyByb3VuZC1yb2Jpbikg4oaSIOybjOy7pCDshKDtg51cbiAqIC0gTWFwPHRva2VuLCBXb3JrZXJbXT7roZwg7Yag7YGw67OEIOybjOy7pCDqtIDrpqxcbiAqIC0g7Yag7YGwIOqwhCBxdW90YSDqt6Drk7Eg7IaM7KeEXG4gKi9cbmltcG9ydCB7IHR5cGUgQ2xpUmVzdWx0LCB0eXBlIFBvb2xDb25maWcsIHR5cGUgUXVlcnlJbnB1dCwgdHlwZSBUb2tlblN0YXRzIH0gZnJvbSBcIi4vcWdyaWQudHlwZXNcIjtcbmltcG9ydCB7IFF1b3RhRXJyb3IgfSBmcm9tIFwiLi9xZ3JpZC50eXBlc1wiO1xuaW1wb3J0IHsgV29ya2VyLCB0eXBlIFdvcmtlckNvbmZpZyB9IGZyb20gXCIuL3dvcmtlclwiO1xuXG5leHBvcnQgY2xhc3MgQ2xhdWRlUG9vbCB7XG4gIHdvcmtlcnMgPSBuZXcgTWFwPHN0cmluZywgV29ya2VyW10+KCk7XG4gIHRva2VuTmFtZXMgPSBuZXcgTWFwPHN0cmluZywgc3RyaW5nPigpO1xuICBxdW90YUV4aGF1c3RlZCA9IG5ldyBTZXQ8c3RyaW5nPigpO1xuICByZXF1ZXN0Q291bnRzID0gbmV3IE1hcDxzdHJpbmcsIG51bWJlcj4oKTtcbiAgbGFzdFVzZWRUb2tlbiA9IFwiXCI7XG4gIGxhc3RVc2VkV29ya2VySW5kZXggPSAwO1xuICBzaXplOiBudW1iZXI7XG4gIG1vZGVsOiBzdHJpbmc7XG4gIHRpbWVvdXQ6IG51bWJlcjtcbiAgY3dkOiBzdHJpbmc7XG4gIG1heENhbGxzOiBudW1iZXI7XG5cbiAgY29uc3RydWN0b3IoY29uZmlnOiBQb29sQ29uZmlnKSB7XG4gICAgdGhpcy5zaXplID0gY29uZmlnLnNpemUgPz8gMztcbiAgICB0aGlzLm1vZGVsID0gY29uZmlnLm1vZGVsID8/IFwic29ubmV0XCI7XG4gICAgdGhpcy50aW1lb3V0ID0gY29uZmlnLnRpbWVvdXQgPz8gMzAwXzAwMDtcbiAgICB0aGlzLmN3ZCA9IGNvbmZpZy5jd2QgPz8gXCIvdG1wL3FncmlkXCI7XG4gICAgdGhpcy5tYXhDYWxscyA9IGNvbmZpZy5tYXhDYWxscyA/PyA1MDA7XG5cbiAgICBjb25maWcudG9rZW5zLmZvckVhY2goKHsgdG9rZW4sIG5hbWUgfSkgPT4ge1xuICAgICAgdGhpcy5jcmVhdGVXb3JrZXJzKHRva2VuLCBuYW1lKTtcbiAgICB9KTtcbiAgfVxuXG4gIGdldExhc3RVc2VkV29ya2VyTmFtZSgpOiBzdHJpbmcge1xuICAgIGNvbnN0IGJhc2VOYW1lID0gdGhpcy50b2tlbk5hbWVzLmdldCh0aGlzLmxhc3RVc2VkVG9rZW4pID8/IFwiVW5rbm93blwiO1xuICAgIHJldHVybiBgJHtiYXNlTmFtZX0tJHt0aGlzLmxhc3RVc2VkV29ya2VySW5kZXh9YDtcbiAgfVxuXG4gIGdldFN0YXRzKCk6IFRva2VuU3RhdHNbXSB7XG4gICAgcmV0dXJuIFsuLi50aGlzLndvcmtlcnMua2V5cygpXS5tYXAoKHRva2VuKSA9PiAoe1xuICAgICAgdG9rZW4sXG4gICAgICBuYW1lOiB0aGlzLnRva2VuTmFtZXMuZ2V0KHRva2VuKSA/PyBcIlVua25vd25cIixcbiAgICAgIHJlcXVlc3RzOiB0aGlzLnJlcXVlc3RDb3VudHMuZ2V0KHRva2VuKSA/PyAwLFxuICAgICAgYWN0aXZlOiAhdGhpcy5xdW90YUV4aGF1c3RlZC5oYXModG9rZW4pLFxuICAgIH0pKTtcbiAgfVxuXG4gIHByaXZhdGUgdG9rZW5SckluZGV4ID0gMDtcbiAgcHJpdmF0ZSB3b3JrZXJSckluZGV4ZXMgPSBuZXcgTWFwPHN0cmluZywgbnVtYmVyPigpO1xuXG4gIHNlbGVjdFdvcmtlcigpOiBXb3JrZXIgfCBudWxsIHtcbiAgICBjb25zdCBhdmFpbGFibGVUb2tlbnMgPSBbLi4udGhpcy53b3JrZXJzLmVudHJpZXMoKV0uZmlsdGVyKFxuICAgICAgKFt0b2tlbl0pID0+ICF0aGlzLnF1b3RhRXhoYXVzdGVkLmhhcyh0b2tlbiksXG4gICAgKTtcblxuICAgIGlmIChhdmFpbGFibGVUb2tlbnMubGVuZ3RoID09PSAwKSByZXR1cm4gbnVsbDtcblxuICAgIC8vIDHri6jqs4Q6IO2GoO2BsOuzhCDstJ0g7YGQIGRlcHRo6rCAIOqwgOyepSDrgq7snYAg7Yag7YGwIOyEoO2DnVxuICAgIGNvbnN0IHRva2VuRGVwdGhzID0gYXZhaWxhYmxlVG9rZW5zLm1hcCgoW3Rva2VuLCB3b3JrZXJzXSkgPT4gKHtcbiAgICAgIHRva2VuLFxuICAgICAgd29ya2VycyxcbiAgICAgIHRvdGFsRGVwdGg6IHdvcmtlcnMucmVkdWNlKChzdW0sIHcpID0+IHN1bSArIHcuZ2V0UXVldWVEZXB0aCgpLCAwKSxcbiAgICB9KSk7XG4gICAgY29uc3QgbWluVG9rZW5EZXB0aCA9IE1hdGgubWluKC4uLnRva2VuRGVwdGhzLm1hcCgodCkgPT4gdC50b3RhbERlcHRoKSk7XG4gICAgY29uc3QgaWRsZVRva2VucyA9IHRva2VuRGVwdGhzLmZpbHRlcigodCkgPT4gdC50b3RhbERlcHRoID09PSBtaW5Ub2tlbkRlcHRoKTtcbiAgICBjb25zdCBzZWxlY3RlZCA9IGlkbGVUb2tlbnNbdGhpcy50b2tlblJySW5kZXggJSBpZGxlVG9rZW5zLmxlbmd0aF0hO1xuICAgIHRoaXMudG9rZW5SckluZGV4Kys7XG5cbiAgICAvLyAy64uo6rOEOiDthqDtgbAg64K07JeQ7IScIGxlYXN0LWRlcHRoIOybjOy7pCwg64+Z66Wg7J2066m0IHJvdW5kLXJvYmluXG4gICAgY29uc3QgbWluRGVwdGggPSBNYXRoLm1pbiguLi5zZWxlY3RlZC53b3JrZXJzLm1hcCgodykgPT4gdy5nZXRRdWV1ZURlcHRoKCkpKTtcbiAgICBjb25zdCBpZGxlV29ya2VycyA9IHNlbGVjdGVkLndvcmtlcnMuZmlsdGVyKCh3KSA9PiB3LmdldFF1ZXVlRGVwdGgoKSA9PT0gbWluRGVwdGgpO1xuICAgIGNvbnN0IHdySWR4ID0gdGhpcy53b3JrZXJSckluZGV4ZXMuZ2V0KHNlbGVjdGVkLnRva2VuKSA/PyAwO1xuICAgIGNvbnN0IHBpY2tlZCA9IGlkbGVXb3JrZXJzW3dySWR4ICUgaWRsZVdvcmtlcnMubGVuZ3RoXSE7XG4gICAgdGhpcy53b3JrZXJSckluZGV4ZXMuc2V0KHNlbGVjdGVkLnRva2VuLCB3cklkeCArIDEpO1xuXG4gICAgcmV0dXJuIHBpY2tlZDtcbiAgfVxuXG4gIGFzeW5jIHF1ZXJ5KGlucHV0OiBRdWVyeUlucHV0LCB0aW1lb3V0TXM/OiBudW1iZXIpOiBQcm9taXNlPENsaVJlc3VsdD4ge1xuICAgIGNvbnN0IHRyaWVkVG9rZW5zID0gbmV3IFNldDxzdHJpbmc+KCk7XG5cbiAgICB3aGlsZSAodHJ1ZSkge1xuICAgICAgY29uc3Qgd29ya2VyID0gdGhpcy5zZWxlY3RXb3JrZXIoKTtcbiAgICAgIGlmICghd29ya2VyKSB7XG4gICAgICAgIHRocm93IG5ldyBRdW90YUVycm9yKFwiQWxsIHRva2VucyBleGhhdXN0ZWRcIik7XG4gICAgICB9XG5cbiAgICAgIGlmICh0cmllZFRva2Vucy5oYXMod29ya2VyLnRva2VuSWQpKSB7XG4gICAgICAgIHRocm93IG5ldyBRdW90YUVycm9yKFwiQWxsIHRva2VucyBleGhhdXN0ZWRcIik7XG4gICAgICB9XG5cbiAgICAgIGNvbnN0IHdvcmtlck5hbWUgPSB0aGlzLnRva2VuTmFtZXMuZ2V0KHdvcmtlci50b2tlbklkKSA/PyBcIlVua25vd25cIjtcbiAgICAgIGNvbnN0IHRva2VuV29ya2VycyA9IHRoaXMud29ya2Vycy5nZXQod29ya2VyLnRva2VuSWQpO1xuICAgICAgY29uc3Qgd29ya2VySWR4ID0gdG9rZW5Xb3JrZXJzID8gdG9rZW5Xb3JrZXJzLmluZGV4T2Yod29ya2VyKSArIDEgOiAwO1xuICAgICAgY29uc29sZS5sb2coYFtxZ3JpZF0g4oaSICR7d29ya2VyTmFtZX0tJHt3b3JrZXJJZHh9IChxdWV1ZTogJHt3b3JrZXIuZ2V0UXVldWVEZXB0aCgpfSlgKTtcblxuICAgICAgdHJ5IHtcbiAgICAgICAgY29uc3QgcmVzdWx0ID0gYXdhaXQgd29ya2VyLnF1ZXJ5KGlucHV0LCB0aW1lb3V0TXMpO1xuICAgICAgICB0aGlzLmxhc3RVc2VkVG9rZW4gPSB3b3JrZXIudG9rZW5JZDtcbiAgICAgICAgdGhpcy5sYXN0VXNlZFdvcmtlckluZGV4ID0gd29ya2VySWR4O1xuICAgICAgICB0aGlzLnJlcXVlc3RDb3VudHMuc2V0KHdvcmtlci50b2tlbklkLCAodGhpcy5yZXF1ZXN0Q291bnRzLmdldCh3b3JrZXIudG9rZW5JZCkgPz8gMCkgKyAxKTtcblxuICAgICAgICByZXR1cm4gcmVzdWx0O1xuICAgICAgfSBjYXRjaCAoZXJyKSB7XG4gICAgICAgIGlmIChlcnIgaW5zdGFuY2VvZiBRdW90YUVycm9yKSB7XG4gICAgICAgICAgdGhpcy5xdW90YUV4aGF1c3RlZC5hZGQod29ya2VyLnRva2VuSWQpO1xuICAgICAgICAgIHRyaWVkVG9rZW5zLmFkZCh3b3JrZXIudG9rZW5JZCk7XG4gICAgICAgICAgY29udGludWU7XG4gICAgICAgIH1cbiAgICAgICAgdGhyb3cgZXJyO1xuICAgICAgfVxuICAgIH1cbiAgfVxuXG4gIGtpbGwoKTogdm9pZCB7XG4gICAgWy4uLnRoaXMud29ya2Vycy52YWx1ZXMoKV0uZmxhdCgpLmZvckVhY2goKHcpID0+IHtcbiAgICAgIHcua2lsbCgpO1xuICAgIH0pO1xuICB9XG5cbiAgY3JlYXRlV29ya2Vycyh0b2tlbjogc3RyaW5nLCBuYW1lOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBpZiAodGhpcy53b3JrZXJzLmhhcyh0b2tlbikpIHJldHVybjtcblxuICAgIHRoaXMudG9rZW5OYW1lcy5zZXQodG9rZW4sIG5hbWUpO1xuICAgIGNvbnN0IHdvcmtlckNvbmZpZzogV29ya2VyQ29uZmlnID0ge1xuICAgICAgdG9rZW4sXG4gICAgICBtb2RlbDogdGhpcy5tb2RlbCxcbiAgICAgIHRpbWVvdXQ6IHRoaXMudGltZW91dCxcbiAgICAgIGN3ZDogdGhpcy5jd2QsXG4gICAgICBtYXhDYWxsczogdGhpcy5tYXhDYWxscyxcbiAgICB9O1xuICAgIGNvbnN0IHdvcmtlcnMgPSBBcnJheS5mcm9tKHsgbGVuZ3RoOiB0aGlzLnNpemUgfSwgKCkgPT4gbmV3IFdvcmtlcih3b3JrZXJDb25maWcpKTtcbiAgICB0aGlzLndvcmtlcnMuc2V0KHRva2VuLCB3b3JrZXJzKTtcbiAgICB0aGlzLnJlcXVlc3RDb3VudHMuc2V0KHRva2VuLCAwKTtcbiAgfVxuXG4gIGRlc3Ryb3lXb3JrZXJzKHRva2VuOiBzdHJpbmcpOiB2b2lkIHtcbiAgICBjb25zdCB3b3JrZXJzID0gdGhpcy53b3JrZXJzLmdldCh0b2tlbik7XG4gICAgaWYgKCF3b3JrZXJzKSByZXR1cm47XG5cbiAgICB3b3JrZXJzLmZvckVhY2goKHcpID0+IHtcbiAgICAgIHcua2lsbCgpO1xuICAgIH0pO1xuICAgIHRoaXMud29ya2Vycy5kZWxldGUodG9rZW4pO1xuICAgIHRoaXMudG9rZW5OYW1lcy5kZWxldGUodG9rZW4pO1xuICAgIHRoaXMucXVvdGFFeGhhdXN0ZWQuZGVsZXRlKHRva2VuKTtcbiAgICB0aGlzLnJlcXVlc3RDb3VudHMuZGVsZXRlKHRva2VuKTtcbiAgfVxufVxuXG4vLyBQb29sIOyLseq4gO2EtCDqtIDrpqxcbmxldCBwb29sOiBDbGF1ZGVQb29sIHwgbnVsbCA9IG51bGw7XG5cbmV4cG9ydCBmdW5jdGlvbiBpbml0UG9vbCh0b2tlbnM6IHsgdG9rZW46IHN0cmluZzsgbmFtZTogc3RyaW5nIH1bXSk6IENsYXVkZVBvb2wge1xuICBwb29sID0gbmV3IENsYXVkZVBvb2woeyB0b2tlbnMgfSk7XG4gIHJldHVybiBwb29sO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gZ2V0UG9vbCgpOiBDbGF1ZGVQb29sIHtcbiAgaWYgKCFwb29sKSB0aHJvdyBuZXcgRXJyb3IoXCJQb29sIG5vdCBpbml0aWFsaXplZC4gQ2FsbCBpbml0UG9vbCgpIGZpcnN0LlwiKTtcbiAgcmV0dXJuIHBvb2w7XG59XG4iXSwibWFwcGluZ3MiOiI7Ozs7QUFXQSxJQUFhLGFBQWIsTUFBd0I7Q0FDdEIsMEJBQVUsSUFBSSxLQUF1QjtDQUNyQyw2QkFBYSxJQUFJLEtBQXFCO0NBQ3RDLGlDQUFpQixJQUFJLEtBQWE7Q0FDbEMsZ0NBQWdCLElBQUksS0FBcUI7Q0FDekMsZ0JBQWdCO0NBQ2hCLHNCQUFzQjtDQUN0QjtDQUNBO0NBQ0E7Q0FDQTtDQUNBO0NBRUEsWUFBWSxRQUFvQjtBQUM5QixPQUFLLE9BQU8sT0FBTyxRQUFRO0FBQzNCLE9BQUssUUFBUSxPQUFPLFNBQVM7QUFDN0IsT0FBSyxVQUFVLE9BQU8sV0FBVztBQUNqQyxPQUFLLE1BQU0sT0FBTyxPQUFPO0FBQ3pCLE9BQUssV0FBVyxPQUFPLFlBQVk7QUFFbkMsU0FBTyxPQUFPLFNBQVMsRUFBRSxPQUFPLFdBQVc7QUFDekMsUUFBSyxjQUFjLE9BQU8sS0FBSztJQUMvQjs7Q0FHSix3QkFBZ0M7QUFFOUIsU0FBTyxHQURVLEtBQUssV0FBVyxJQUFJLEtBQUssY0FBYyxJQUFJLFVBQ3pDLEdBQUcsS0FBSzs7Q0FHN0IsV0FBeUI7QUFDdkIsU0FBTyxDQUFDLEdBQUcsS0FBSyxRQUFRLE1BQU0sQ0FBQyxDQUFDLEtBQUssV0FBVztHQUM5QztHQUNBLE1BQU0sS0FBSyxXQUFXLElBQUksTUFBTSxJQUFJO0dBQ3BDLFVBQVUsS0FBSyxjQUFjLElBQUksTUFBTSxJQUFJO0dBQzNDLFFBQVEsQ0FBQyxLQUFLLGVBQWUsSUFBSSxNQUFNO0dBQ3hDLEVBQUU7O0NBR0wsZUFBdUI7Q0FDdkIsa0NBQTBCLElBQUksS0FBcUI7Q0FFbkQsZUFBOEI7RUFDNUIsTUFBTSxrQkFBa0IsQ0FBQyxHQUFHLEtBQUssUUFBUSxTQUFTLENBQUMsQ0FBQyxRQUNqRCxDQUFDLFdBQVcsQ0FBQyxLQUFLLGVBQWUsSUFBSSxNQUFNLENBQzdDO0FBRUQsTUFBSSxnQkFBZ0IsV0FBVyxFQUFHLFFBQU87RUFHekMsTUFBTSxjQUFjLGdCQUFnQixLQUFLLENBQUMsT0FBTyxjQUFjO0dBQzdEO0dBQ0E7R0FDQSxZQUFZLFFBQVEsUUFBUSxLQUFLLE1BQU0sTUFBTSxFQUFFLGVBQWUsRUFBRSxFQUFFO0dBQ25FLEVBQUU7RUFDSCxNQUFNLGdCQUFnQixLQUFLLElBQUksR0FBRyxZQUFZLEtBQUssTUFBTSxFQUFFLFdBQVcsQ0FBQztFQUN2RSxNQUFNLGFBQWEsWUFBWSxRQUFRLE1BQU0sRUFBRSxlQUFlLGNBQWM7RUFDNUUsTUFBTSxXQUFXLFdBQVcsS0FBSyxlQUFlLFdBQVc7QUFDM0QsT0FBSztFQUdMLE1BQU0sV0FBVyxLQUFLLElBQUksR0FBRyxTQUFTLFFBQVEsS0FBSyxNQUFNLEVBQUUsZUFBZSxDQUFDLENBQUM7RUFDNUUsTUFBTSxjQUFjLFNBQVMsUUFBUSxRQUFRLE1BQU0sRUFBRSxlQUFlLEtBQUssU0FBUztFQUNsRixNQUFNLFFBQVEsS0FBSyxnQkFBZ0IsSUFBSSxTQUFTLE1BQU0sSUFBSTtFQUMxRCxNQUFNLFNBQVMsWUFBWSxRQUFRLFlBQVk7QUFDL0MsT0FBSyxnQkFBZ0IsSUFBSSxTQUFTLE9BQU8sUUFBUSxFQUFFO0FBRW5ELFNBQU87O0NBR1QsTUFBTSxNQUFNLE9BQW1CLFdBQXdDO0VBQ3JFLE1BQU0sOEJBQWMsSUFBSSxLQUFhO0FBRXJDLFNBQU8sTUFBTTtHQUNYLE1BQU0sU0FBUyxLQUFLLGNBQWM7QUFDbEMsT0FBSSxDQUFDLE9BQ0gsT0FBTSxJQUFJLFdBQVcsdUJBQXVCO0FBRzlDLE9BQUksWUFBWSxJQUFJLE9BQU8sUUFBUSxDQUNqQyxPQUFNLElBQUksV0FBVyx1QkFBdUI7R0FHOUMsTUFBTSxhQUFhLEtBQUssV0FBVyxJQUFJLE9BQU8sUUFBUSxJQUFJO0dBQzFELE1BQU0sZUFBZSxLQUFLLFFBQVEsSUFBSSxPQUFPLFFBQVE7R0FDckQsTUFBTSxZQUFZLGVBQWUsYUFBYSxRQUFRLE9BQU8sR0FBRyxJQUFJO0FBQ3BFLFdBQVEsSUFBSSxhQUFhLFdBQVcsR0FBRyxVQUFVLFdBQVcsT0FBTyxlQUFlLENBQUMsR0FBRztBQUV0RixPQUFJO0lBQ0YsTUFBTSxTQUFTLE1BQU0sT0FBTyxNQUFNLE9BQU8sVUFBVTtBQUNuRCxTQUFLLGdCQUFnQixPQUFPO0FBQzVCLFNBQUssc0JBQXNCO0FBQzNCLFNBQUssY0FBYyxJQUFJLE9BQU8sVUFBVSxLQUFLLGNBQWMsSUFBSSxPQUFPLFFBQVEsSUFBSSxLQUFLLEVBQUU7QUFFekYsV0FBTztZQUNBLEtBQUs7QUFDWixRQUFJLGVBQWUsWUFBWTtBQUM3QixVQUFLLGVBQWUsSUFBSSxPQUFPLFFBQVE7QUFDdkMsaUJBQVksSUFBSSxPQUFPLFFBQVE7QUFDL0I7O0FBRUYsVUFBTTs7OztDQUtaLE9BQWE7QUFDWCxHQUFDLEdBQUcsS0FBSyxRQUFRLFFBQVEsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxTQUFTLE1BQU07QUFDL0MsS0FBRSxNQUFNO0lBQ1I7O0NBR0osY0FBYyxPQUFlLE1BQW9CO0FBQy9DLE1BQUksS0FBSyxRQUFRLElBQUksTUFBTSxDQUFFO0FBRTdCLE9BQUssV0FBVyxJQUFJLE9BQU8sS0FBSztFQUNoQyxNQUFNLGVBQTZCO0dBQ2pDO0dBQ0EsT0FBTyxLQUFLO0dBQ1osU0FBUyxLQUFLO0dBQ2QsS0FBSyxLQUFLO0dBQ1YsVUFBVSxLQUFLO0dBQ2hCO0VBQ0QsTUFBTSxVQUFVLE1BQU0sS0FBSyxFQUFFLFFBQVEsS0FBSyxNQUFNLFFBQVEsSUFBSSxPQUFPLGFBQWEsQ0FBQztBQUNqRixPQUFLLFFBQVEsSUFBSSxPQUFPLFFBQVE7QUFDaEMsT0FBSyxjQUFjLElBQUksT0FBTyxFQUFFOztDQUdsQyxlQUFlLE9BQXFCO0VBQ2xDLE1BQU0sVUFBVSxLQUFLLFFBQVEsSUFBSSxNQUFNO0FBQ3ZDLE1BQUksQ0FBQyxRQUFTO0FBRWQsVUFBUSxTQUFTLE1BQU07QUFDckIsS0FBRSxNQUFNO0lBQ1I7QUFDRixPQUFLLFFBQVEsT0FBTyxNQUFNO0FBQzFCLE9BQUssV0FBVyxPQUFPLE1BQU07QUFDN0IsT0FBSyxlQUFlLE9BQU8sTUFBTTtBQUNqQyxPQUFLLGNBQWMsT0FBTyxNQUFNOzs7QUFLcEMsSUFBSSxPQUEwQjtBQUU5QixTQUFnQixTQUFTLFFBQXVEO0FBQzlFLFFBQU8sSUFBSSxXQUFXLEVBQUUsUUFBUSxDQUFDO0FBQ2pDLFFBQU87O0FBR1QsU0FBZ0IsVUFBc0I7QUFDcEMsS0FBSSxDQUFDLEtBQU0sT0FBTSxJQUFJLE1BQU0sK0NBQStDO0FBQzFFLFFBQU8ifQ==
@@ -1,189 +0,0 @@
1
- import "../../_virtual/_rolldown/runtime.js";
2
- import { ProcessError, QuotaError, TimeoutError, maskToken } from "./qgrid.types.js";
3
- import { spawn } from "node:child_process";
4
- import { mkdirSync } from "node:fs";
5
- //#region src/application/qgrid/worker.ts
6
- /**
7
- * Worker — Claude CLI stream-json 프로세스 하나를 관리.
8
- *
9
- * 기존 SocratsAI ClaudeCliService에서 추출 + 리팩터:
10
- * - 토큰/모델을 생성자에서 주입
11
- * - env allowlist (PATH, TMPDIR, CLAUDE_CODE_OAUTH_TOKEN + CLAUDE_CODE_DISABLE_* )
12
- * - callCount 추적 + maxCalls 도달 시 프로세스 재활용
13
- * - QuotaError/TimeoutError/ProcessError 구분
14
- */
15
- var Worker = class {
16
- tokenId;
17
- config;
18
- process = null;
19
- buffer = "";
20
- pending = null;
21
- sessionId = null;
22
- queue = [];
23
- cwdCreated = false;
24
- callCount = 0;
25
- needsRecycle = false;
26
- constructor(config) {
27
- this.config = config;
28
- this.tokenId = config.token;
29
- }
30
- getQueueDepth() {
31
- return this.queue.length + (this.pending ? 1 : 0);
32
- }
33
- rejectPending(err) {
34
- if (!this.pending) return;
35
- this.pending.reject(err);
36
- clearTimeout(this.pending.timer);
37
- this.pending = null;
38
- }
39
- ensureProcess() {
40
- if (this.process && this.process.exitCode === null && !this.needsRecycle) return this.process;
41
- if (this.process && this.process.exitCode === null) {
42
- this.process.kill();
43
- this.process = null;
44
- }
45
- const env = {
46
- PATH: process.env.PATH,
47
- TMPDIR: process.env.TMPDIR,
48
- CLAUDE_CODE_OAUTH_TOKEN: this.config.token,
49
- CLAUDE_CODE_DISABLE_AUTO_MEMORY: "1",
50
- CLAUDE_CODE_DISABLE_ADAPTIVE_THINKING: "1",
51
- CLAUDE_CODE_DISABLE_1M_CONTEXT: "1"
52
- };
53
- const { cwd } = this.config;
54
- if (!this.cwdCreated) {
55
- mkdirSync(cwd, { recursive: true });
56
- this.cwdCreated = true;
57
- }
58
- const child = spawn("claude", [
59
- "-p",
60
- "--input-format",
61
- "stream-json",
62
- "--output-format",
63
- "stream-json",
64
- "--verbose",
65
- "--max-turns",
66
- "1",
67
- "--permission-mode",
68
- "bypassPermissions",
69
- "--setting-sources",
70
- "project",
71
- "--model",
72
- this.config.model
73
- ], {
74
- stdio: [
75
- "pipe",
76
- "pipe",
77
- "ignore"
78
- ],
79
- env,
80
- cwd
81
- });
82
- child.stdout?.on("data", (d) => {
83
- this.buffer += d.toString();
84
- this.processBuffer();
85
- });
86
- child.on("close", () => {
87
- this.process = null;
88
- this.rejectPending(new ProcessError(`CLI process closed (token: ${maskToken(this.tokenId)})`));
89
- this.drainQueue();
90
- });
91
- child.on("error", (err) => {
92
- this.process = null;
93
- this.rejectPending(new ProcessError(`CLI process error: ${err.message} (token: ${maskToken(this.tokenId)})`));
94
- this.drainQueue();
95
- });
96
- this.process = child;
97
- this.buffer = "";
98
- this.sessionId = null;
99
- this.callCount = 0;
100
- this.needsRecycle = false;
101
- return child;
102
- }
103
- processBuffer() {
104
- const lines = this.buffer.split("\n");
105
- this.buffer = lines.pop() ?? "";
106
- lines.filter((line) => line.trim() !== "").forEach((line) => {
107
- try {
108
- const j = JSON.parse(line);
109
- if (j.type === "system" && j.subtype === "init" && j.session_id) this.sessionId = j.session_id;
110
- if (j.type === "result" && this.pending) {
111
- let text = j.result ?? "";
112
- text = text.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?```\s*$/i, "");
113
- if (text.startsWith("You've hit")) {
114
- this.pending.reject(new QuotaError(`Quota exhausted (token: ${maskToken(this.tokenId)})`));
115
- clearTimeout(this.pending.timer);
116
- this.pending = null;
117
- this.drainQueue();
118
- return;
119
- }
120
- const u = j.usage ?? {};
121
- this.pending.resolve({
122
- text,
123
- usage: {
124
- input_tokens: u.input_tokens ?? 0,
125
- output_tokens: u.output_tokens ?? 0,
126
- cache_creation_input_tokens: u.cache_creation_input_tokens ?? 0,
127
- cache_read_input_tokens: u.cache_read_input_tokens ?? 0
128
- },
129
- durationMs: j.duration_ms ?? 0,
130
- costUsd: j.total_cost_usd ?? 0
131
- });
132
- clearTimeout(this.pending.timer);
133
- this.pending = null;
134
- this.drainQueue();
135
- }
136
- } catch {}
137
- });
138
- }
139
- drainQueue() {
140
- if (this.pending) return;
141
- const next = this.queue.shift();
142
- if (next) next();
143
- }
144
- async query(input, timeoutMs) {
145
- if (this.pending) await new Promise((resolve) => {
146
- this.queue.push(resolve);
147
- });
148
- const child = this.ensureProcess();
149
- const timeout = timeoutMs ?? this.config.timeout;
150
- this.callCount++;
151
- if (this.callCount >= this.config.maxCalls) this.needsRecycle = true;
152
- const prompt = input.system ? `${input.system}\n\n${input.prompt}` : input.prompt;
153
- return new Promise((resolve, reject) => {
154
- this.pending = {
155
- resolve,
156
- reject,
157
- timer: setTimeout(() => {
158
- this.pending = null;
159
- reject(new TimeoutError(`Timeout after ${timeout / 1e3}s (token: ${maskToken(this.tokenId)})`));
160
- this.kill();
161
- }, timeout)
162
- };
163
- const msg = JSON.stringify({
164
- type: "user",
165
- message: {
166
- role: "user",
167
- content: prompt
168
- },
169
- session_id: this.sessionId ?? "default",
170
- parent_tool_use_id: null
171
- });
172
- child.stdin?.write(`${msg}\n`);
173
- });
174
- }
175
- kill() {
176
- if (this.process && this.process.exitCode === null) this.process.kill();
177
- this.process = null;
178
- this.buffer = "";
179
- this.sessionId = null;
180
- this.callCount = 0;
181
- this.needsRecycle = false;
182
- this.rejectPending(new ProcessError(`Worker killed (token: ${maskToken(this.tokenId)})`));
183
- this.drainQueue();
184
- }
185
- };
186
- //#endregion
187
- export { Worker };
188
-
189
- //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2VyLmpzIiwibmFtZXMiOltdLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3NyYy9hcHBsaWNhdGlvbi9xZ3JpZC93b3JrZXIudHMiXSwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBXb3JrZXIg4oCUIENsYXVkZSBDTEkgc3RyZWFtLWpzb24g7ZSE66Gc7IS47IqkIO2VmOuCmOulvCDqtIDrpqwuXG4gKlxuICog6riw7KG0IFNvY3JhdHNBSSBDbGF1ZGVDbGlTZXJ2aWNl7JeQ7IScIOy2lOy2nCArIOumrO2Mqe2EsDpcbiAqIC0g7Yag7YGwL+uqqOuNuOydhCDsg53shLHsnpDsl5DshJwg7KO87J6FXG4gKiAtIGVudiBhbGxvd2xpc3QgKFBBVEgsIFRNUERJUiwgQ0xBVURFX0NPREVfT0FVVEhfVE9LRU4gKyBDTEFVREVfQ09ERV9ESVNBQkxFXyogKVxuICogLSBjYWxsQ291bnQg7LaU7KCBICsgbWF4Q2FsbHMg64+E64usIOyLnCDtlITroZzshLjsiqQg7J6s7Zmc7JqpXG4gKiAtIFF1b3RhRXJyb3IvVGltZW91dEVycm9yL1Byb2Nlc3NFcnJvciDqtazrtoRcbiAqL1xuaW1wb3J0IHsgdHlwZSBDaGlsZFByb2Nlc3MsIHNwYXduIH0gZnJvbSBcIm5vZGU6Y2hpbGRfcHJvY2Vzc1wiO1xuaW1wb3J0IHsgbWtkaXJTeW5jIH0gZnJvbSBcIm5vZGU6ZnNcIjtcblxuaW1wb3J0IHsgdHlwZSBDbGlSZXN1bHQsIHR5cGUgUXVlcnlJbnB1dCB9IGZyb20gXCIuL3FncmlkLnR5cGVzXCI7XG5pbXBvcnQgeyBtYXNrVG9rZW4sIFByb2Nlc3NFcnJvciwgUXVvdGFFcnJvciwgVGltZW91dEVycm9yIH0gZnJvbSBcIi4vcWdyaWQudHlwZXNcIjtcblxudHlwZSBQZW5kaW5nUmVxdWVzdCA9IHtcbiAgcmVzb2x2ZTogKHZhbHVlOiBDbGlSZXN1bHQpID0+IHZvaWQ7XG4gIHJlamVjdDogKHJlYXNvbjogRXJyb3IpID0+IHZvaWQ7XG4gIHRpbWVyOiBSZXR1cm5UeXBlPHR5cGVvZiBzZXRUaW1lb3V0Pjtcbn07XG5cbmV4cG9ydCB0eXBlIFdvcmtlckNvbmZpZyA9IHtcbiAgdG9rZW46IHN0cmluZztcbiAgbW9kZWw6IHN0cmluZztcbiAgdGltZW91dDogbnVtYmVyO1xuICBjd2Q6IHN0cmluZztcbiAgbWF4Q2FsbHM6IG51bWJlcjtcbn07XG5cbmV4cG9ydCBjbGFzcyBXb3JrZXIge1xuICB0b2tlbklkOiBzdHJpbmc7XG4gIGNvbmZpZzogV29ya2VyQ29uZmlnO1xuICBwcm9jZXNzOiBDaGlsZFByb2Nlc3MgfCBudWxsID0gbnVsbDtcbiAgYnVmZmVyID0gXCJcIjtcbiAgcGVuZGluZzogUGVuZGluZ1JlcXVlc3QgfCBudWxsID0gbnVsbDtcbiAgc2Vzc2lvbklkOiBzdHJpbmcgfCBudWxsID0gbnVsbDtcbiAgcXVldWU6IEFycmF5PCgpID0+IHZvaWQ+ID0gW107XG4gIGN3ZENyZWF0ZWQgPSBmYWxzZTtcbiAgY2FsbENvdW50ID0gMDtcbiAgbmVlZHNSZWN5Y2xlID0gZmFsc2U7XG5cbiAgY29uc3RydWN0b3IoY29uZmlnOiBXb3JrZXJDb25maWcpIHtcbiAgICB0aGlzLmNvbmZpZyA9IGNvbmZpZztcbiAgICB0aGlzLnRva2VuSWQgPSBjb25maWcudG9rZW47XG4gIH1cblxuICBnZXRRdWV1ZURlcHRoKCk6IG51bWJlciB7XG4gICAgcmV0dXJuIHRoaXMucXVldWUubGVuZ3RoICsgKHRoaXMucGVuZGluZyA/IDEgOiAwKTtcbiAgfVxuXG4gIHJlamVjdFBlbmRpbmcoZXJyOiBFcnJvcik6IHZvaWQge1xuICAgIGlmICghdGhpcy5wZW5kaW5nKSByZXR1cm47XG4gICAgdGhpcy5wZW5kaW5nLnJlamVjdChlcnIpO1xuICAgIGNsZWFyVGltZW91dCh0aGlzLnBlbmRpbmcudGltZXIpO1xuICAgIHRoaXMucGVuZGluZyA9IG51bGw7XG4gIH1cblxuICBlbnN1cmVQcm9jZXNzKCk6IENoaWxkUHJvY2VzcyB7XG4gICAgaWYgKHRoaXMucHJvY2VzcyAmJiB0aGlzLnByb2Nlc3MuZXhpdENvZGUgPT09IG51bGwgJiYgIXRoaXMubmVlZHNSZWN5Y2xlKSB7XG4gICAgICByZXR1cm4gdGhpcy5wcm9jZXNzO1xuICAgIH1cblxuICAgIGlmICh0aGlzLnByb2Nlc3MgJiYgdGhpcy5wcm9jZXNzLmV4aXRDb2RlID09PSBudWxsKSB7XG4gICAgICB0aGlzLnByb2Nlc3Mua2lsbCgpO1xuICAgICAgdGhpcy5wcm9jZXNzID0gbnVsbDtcbiAgICB9XG5cbiAgICBjb25zdCBlbnY6IE5vZGVKUy5Qcm9jZXNzRW52ID0ge1xuICAgICAgUEFUSDogcHJvY2Vzcy5lbnYuUEFUSCxcbiAgICAgIFRNUERJUjogcHJvY2Vzcy5lbnYuVE1QRElSLFxuICAgICAgQ0xBVURFX0NPREVfT0FVVEhfVE9LRU46IHRoaXMuY29uZmlnLnRva2VuLFxuICAgICAgQ0xBVURFX0NPREVfRElTQUJMRV9BVVRPX01FTU9SWTogXCIxXCIsXG4gICAgICBDTEFVREVfQ09ERV9ESVNBQkxFX0FEQVBUSVZFX1RISU5LSU5HOiBcIjFcIixcbiAgICAgIENMQVVERV9DT0RFX0RJU0FCTEVfMU1fQ09OVEVYVDogXCIxXCIsXG4gICAgfTtcblxuICAgIGNvbnN0IHsgY3dkIH0gPSB0aGlzLmNvbmZpZztcbiAgICBpZiAoIXRoaXMuY3dkQ3JlYXRlZCkge1xuICAgICAgbWtkaXJTeW5jKGN3ZCwgeyByZWN1cnNpdmU6IHRydWUgfSk7XG4gICAgICB0aGlzLmN3ZENyZWF0ZWQgPSB0cnVlO1xuICAgIH1cblxuICAgIGNvbnN0IGNoaWxkID0gc3Bhd24oXG4gICAgICBcImNsYXVkZVwiLFxuICAgICAgW1xuICAgICAgICBcIi1wXCIsXG4gICAgICAgIFwiLS1pbnB1dC1mb3JtYXRcIixcbiAgICAgICAgXCJzdHJlYW0tanNvblwiLFxuICAgICAgICBcIi0tb3V0cHV0LWZvcm1hdFwiLFxuICAgICAgICBcInN0cmVhbS1qc29uXCIsXG4gICAgICAgIFwiLS12ZXJib3NlXCIsXG4gICAgICAgIFwiLS1tYXgtdHVybnNcIixcbiAgICAgICAgXCIxXCIsXG4gICAgICAgIFwiLS1wZXJtaXNzaW9uLW1vZGVcIixcbiAgICAgICAgXCJieXBhc3NQZXJtaXNzaW9uc1wiLFxuICAgICAgICBcIi0tc2V0dGluZy1zb3VyY2VzXCIsXG4gICAgICAgIFwicHJvamVjdFwiLFxuICAgICAgICBcIi0tbW9kZWxcIixcbiAgICAgICAgdGhpcy5jb25maWcubW9kZWwsXG4gICAgICBdLFxuICAgICAgeyBzdGRpbzogW1wicGlwZVwiLCBcInBpcGVcIiwgXCJpZ25vcmVcIl0sIGVudiwgY3dkIH0sXG4gICAgKTtcblxuICAgIGNoaWxkLnN0ZG91dD8ub24oXCJkYXRhXCIsIChkOiBCdWZmZXIpID0+IHtcbiAgICAgIHRoaXMuYnVmZmVyICs9IGQudG9TdHJpbmcoKTtcbiAgICAgIHRoaXMucHJvY2Vzc0J1ZmZlcigpO1xuICAgIH0pO1xuXG4gICAgY2hpbGQub24oXCJjbG9zZVwiLCAoKSA9PiB7XG4gICAgICB0aGlzLnByb2Nlc3MgPSBudWxsO1xuICAgICAgdGhpcy5yZWplY3RQZW5kaW5nKFxuICAgICAgICBuZXcgUHJvY2Vzc0Vycm9yKGBDTEkgcHJvY2VzcyBjbG9zZWQgKHRva2VuOiAke21hc2tUb2tlbih0aGlzLnRva2VuSWQpfSlgKSxcbiAgICAgICk7XG4gICAgICB0aGlzLmRyYWluUXVldWUoKTtcbiAgICB9KTtcblxuICAgIGNoaWxkLm9uKFwiZXJyb3JcIiwgKGVycikgPT4ge1xuICAgICAgdGhpcy5wcm9jZXNzID0gbnVsbDtcbiAgICAgIHRoaXMucmVqZWN0UGVuZGluZyhcbiAgICAgICAgbmV3IFByb2Nlc3NFcnJvcihgQ0xJIHByb2Nlc3MgZXJyb3I6ICR7ZXJyLm1lc3NhZ2V9ICh0b2tlbjogJHttYXNrVG9rZW4odGhpcy50b2tlbklkKX0pYCksXG4gICAgICApO1xuICAgICAgdGhpcy5kcmFpblF1ZXVlKCk7XG4gICAgfSk7XG5cbiAgICB0aGlzLnByb2Nlc3MgPSBjaGlsZDtcbiAgICB0aGlzLmJ1ZmZlciA9IFwiXCI7XG4gICAgdGhpcy5zZXNzaW9uSWQgPSBudWxsO1xuICAgIHRoaXMuY2FsbENvdW50ID0gMDtcbiAgICB0aGlzLm5lZWRzUmVjeWNsZSA9IGZhbHNlO1xuICAgIHJldHVybiBjaGlsZDtcbiAgfVxuXG4gIHByb2Nlc3NCdWZmZXIoKTogdm9pZCB7XG4gICAgY29uc3QgbGluZXMgPSB0aGlzLmJ1ZmZlci5zcGxpdChcIlxcblwiKTtcbiAgICB0aGlzLmJ1ZmZlciA9IGxpbmVzLnBvcCgpID8/IFwiXCI7XG5cbiAgICBsaW5lc1xuICAgICAgLmZpbHRlcigobGluZSkgPT4gbGluZS50cmltKCkgIT09IFwiXCIpXG4gICAgICAuZm9yRWFjaCgobGluZSkgPT4ge1xuICAgICAgICB0cnkge1xuICAgICAgICAgIGNvbnN0IGogPSBKU09OLnBhcnNlKGxpbmUpO1xuXG4gICAgICAgICAgaWYgKGoudHlwZSA9PT0gXCJzeXN0ZW1cIiAmJiBqLnN1YnR5cGUgPT09IFwiaW5pdFwiICYmIGouc2Vzc2lvbl9pZCkge1xuICAgICAgICAgICAgdGhpcy5zZXNzaW9uSWQgPSBqLnNlc3Npb25faWQ7XG4gICAgICAgICAgfVxuXG4gICAgICAgICAgaWYgKGoudHlwZSA9PT0gXCJyZXN1bHRcIiAmJiB0aGlzLnBlbmRpbmcpIHtcbiAgICAgICAgICAgIGxldCB0ZXh0OiBzdHJpbmcgPSBqLnJlc3VsdCA/PyBcIlwiO1xuICAgICAgICAgICAgdGV4dCA9IHRleHQucmVwbGFjZSgvXmBgYCg/Ompzb24pP1xccypcXG4/L2ksIFwiXCIpLnJlcGxhY2UoL1xcbj9gYGBcXHMqJC9pLCBcIlwiKTtcblxuICAgICAgICAgICAgaWYgKHRleHQuc3RhcnRzV2l0aChcIllvdSd2ZSBoaXRcIikpIHtcbiAgICAgICAgICAgICAgdGhpcy5wZW5kaW5nLnJlamVjdChcbiAgICAgICAgICAgICAgICBuZXcgUXVvdGFFcnJvcihgUXVvdGEgZXhoYXVzdGVkICh0b2tlbjogJHttYXNrVG9rZW4odGhpcy50b2tlbklkKX0pYCksXG4gICAgICAgICAgICAgICk7XG4gICAgICAgICAgICAgIGNsZWFyVGltZW91dCh0aGlzLnBlbmRpbmcudGltZXIpO1xuICAgICAgICAgICAgICB0aGlzLnBlbmRpbmcgPSBudWxsO1xuICAgICAgICAgICAgICB0aGlzLmRyYWluUXVldWUoKTtcbiAgICAgICAgICAgICAgcmV0dXJuO1xuICAgICAgICAgICAgfVxuXG4gICAgICAgICAgICBjb25zdCB1ID0gai51c2FnZSA/PyB7fTtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZy5yZXNvbHZlKHtcbiAgICAgICAgICAgICAgdGV4dCxcbiAgICAgICAgICAgICAgdXNhZ2U6IHtcbiAgICAgICAgICAgICAgICBpbnB1dF90b2tlbnM6IHUuaW5wdXRfdG9rZW5zID8/IDAsXG4gICAgICAgICAgICAgICAgb3V0cHV0X3Rva2VuczogdS5vdXRwdXRfdG9rZW5zID8/IDAsXG4gICAgICAgICAgICAgICAgY2FjaGVfY3JlYXRpb25faW5wdXRfdG9rZW5zOiB1LmNhY2hlX2NyZWF0aW9uX2lucHV0X3Rva2VucyA/PyAwLFxuICAgICAgICAgICAgICAgIGNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zOiB1LmNhY2hlX3JlYWRfaW5wdXRfdG9rZW5zID8/IDAsXG4gICAgICAgICAgICAgIH0sXG4gICAgICAgICAgICAgIGR1cmF0aW9uTXM6IGouZHVyYXRpb25fbXMgPz8gMCxcbiAgICAgICAgICAgICAgY29zdFVzZDogai50b3RhbF9jb3N0X3VzZCA/PyAwLFxuICAgICAgICAgICAgfSk7XG4gICAgICAgICAgICBjbGVhclRpbWVvdXQodGhpcy5wZW5kaW5nLnRpbWVyKTtcbiAgICAgICAgICAgIHRoaXMucGVuZGluZyA9IG51bGw7XG4gICAgICAgICAgICB0aGlzLmRyYWluUXVldWUoKTtcbiAgICAgICAgICB9XG4gICAgICAgIH0gY2F0Y2gge1xuICAgICAgICAgIC8vIHNraXAgbm9uLWpzb24gbGluZXNcbiAgICAgICAgfVxuICAgICAgfSk7XG4gIH1cblxuICBkcmFpblF1ZXVlKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnBlbmRpbmcpIHJldHVybjtcbiAgICBjb25zdCBuZXh0ID0gdGhpcy5xdWV1ZS5zaGlmdCgpO1xuICAgIGlmIChuZXh0KSBuZXh0KCk7XG4gIH1cblxuICBhc3luYyBxdWVyeShpbnB1dDogUXVlcnlJbnB1dCwgdGltZW91dE1zPzogbnVtYmVyKTogUHJvbWlzZTxDbGlSZXN1bHQ+IHtcbiAgICBpZiAodGhpcy5wZW5kaW5nKSB7XG4gICAgICBhd2FpdCBuZXcgUHJvbWlzZTx2b2lkPigocmVzb2x2ZSkgPT4ge1xuICAgICAgICB0aGlzLnF1ZXVlLnB1c2gocmVzb2x2ZSk7XG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBjb25zdCBjaGlsZCA9IHRoaXMuZW5zdXJlUHJvY2VzcygpO1xuICAgIGNvbnN0IHRpbWVvdXQgPSB0aW1lb3V0TXMgPz8gdGhpcy5jb25maWcudGltZW91dDtcblxuICAgIHRoaXMuY2FsbENvdW50Kys7XG4gICAgaWYgKHRoaXMuY2FsbENvdW50ID49IHRoaXMuY29uZmlnLm1heENhbGxzKSB7XG4gICAgICB0aGlzLm5lZWRzUmVjeWNsZSA9IHRydWU7XG4gICAgfVxuXG4gICAgY29uc3QgcHJvbXB0ID0gaW5wdXQuc3lzdGVtID8gYCR7aW5wdXQuc3lzdGVtfVxcblxcbiR7aW5wdXQucHJvbXB0fWAgOiBpbnB1dC5wcm9tcHQ7XG5cbiAgICByZXR1cm4gbmV3IFByb21pc2U8Q2xpUmVzdWx0PigocmVzb2x2ZSwgcmVqZWN0KSA9PiB7XG4gICAgICBjb25zdCB0aW1lciA9IHNldFRpbWVvdXQoKCkgPT4ge1xuICAgICAgICB0aGlzLnBlbmRpbmcgPSBudWxsO1xuICAgICAgICByZWplY3QoXG4gICAgICAgICAgbmV3IFRpbWVvdXRFcnJvcihgVGltZW91dCBhZnRlciAke3RpbWVvdXQgLyAxMDAwfXMgKHRva2VuOiAke21hc2tUb2tlbih0aGlzLnRva2VuSWQpfSlgKSxcbiAgICAgICAgKTtcbiAgICAgICAgdGhpcy5raWxsKCk7XG4gICAgICB9LCB0aW1lb3V0KTtcblxuICAgICAgdGhpcy5wZW5kaW5nID0geyByZXNvbHZlLCByZWplY3QsIHRpbWVyIH07XG5cbiAgICAgIGNvbnN0IG1zZyA9IEpTT04uc3RyaW5naWZ5KHtcbiAgICAgICAgdHlwZTogXCJ1c2VyXCIsXG4gICAgICAgIG1lc3NhZ2U6IHsgcm9sZTogXCJ1c2VyXCIsIGNvbnRlbnQ6IHByb21wdCB9LFxuICAgICAgICBzZXNzaW9uX2lkOiB0aGlzLnNlc3Npb25JZCA/PyBcImRlZmF1bHRcIixcbiAgICAgICAgcGFyZW50X3Rvb2xfdXNlX2lkOiBudWxsLFxuICAgICAgfSk7XG5cbiAgICAgIGNoaWxkLnN0ZGluPy53cml0ZShgJHttc2d9XFxuYCk7XG4gICAgfSk7XG4gIH1cblxuICBraWxsKCk6IHZvaWQge1xuICAgIGlmICh0aGlzLnByb2Nlc3MgJiYgdGhpcy5wcm9jZXNzLmV4aXRDb2RlID09PSBudWxsKSB7XG4gICAgICB0aGlzLnByb2Nlc3Mua2lsbCgpO1xuICAgIH1cbiAgICB0aGlzLnByb2Nlc3MgPSBudWxsO1xuICAgIHRoaXMuYnVmZmVyID0gXCJcIjtcbiAgICB0aGlzLnNlc3Npb25JZCA9IG51bGw7XG4gICAgdGhpcy5jYWxsQ291bnQgPSAwO1xuICAgIHRoaXMubmVlZHNSZWN5Y2xlID0gZmFsc2U7XG4gICAgdGhpcy5yZWplY3RQZW5kaW5nKG5ldyBQcm9jZXNzRXJyb3IoYFdvcmtlciBraWxsZWQgKHRva2VuOiAke21hc2tUb2tlbih0aGlzLnRva2VuSWQpfSlgKSk7XG4gICAgdGhpcy5kcmFpblF1ZXVlKCk7XG4gIH1cbn1cbiJdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7Ozs7QUE2QkEsSUFBYSxTQUFiLE1BQW9CO0NBQ2xCO0NBQ0E7Q0FDQSxVQUErQjtDQUMvQixTQUFTO0NBQ1QsVUFBaUM7Q0FDakMsWUFBMkI7Q0FDM0IsUUFBMkIsRUFBRTtDQUM3QixhQUFhO0NBQ2IsWUFBWTtDQUNaLGVBQWU7Q0FFZixZQUFZLFFBQXNCO0FBQ2hDLE9BQUss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
@@ -1,165 +0,0 @@
1
- /**
2
- * ClaudePool — 멀티 토큰 프로세스 풀.
3
- *
4
- * 2-layer 라우팅: 토큰 선택 (least-total-depth + round-robin) → 워커 선택
5
- * - Map<token, Worker[]>로 토큰별 워커 관리
6
- * - 토큰 간 quota 균등 소진
7
- */
8
- import { type CliResult, type PoolConfig, type QueryInput, type TokenStats } from "./qgrid.types";
9
- import { QuotaError } from "./qgrid.types";
10
- import { Worker, type WorkerConfig } from "./worker";
11
-
12
- export class ClaudePool {
13
- workers = new Map<string, Worker[]>();
14
- tokenNames = new Map<string, string>();
15
- quotaExhausted = new Set<string>();
16
- requestCounts = new Map<string, number>();
17
- lastUsedToken = "";
18
- lastUsedWorkerIndex = 0;
19
- size: number;
20
- model: string;
21
- timeout: number;
22
- cwd: string;
23
- maxCalls: number;
24
-
25
- constructor(config: PoolConfig) {
26
- this.size = config.size ?? 3;
27
- this.model = config.model ?? "sonnet";
28
- this.timeout = config.timeout ?? 300_000;
29
- this.cwd = config.cwd ?? "/tmp/qgrid";
30
- this.maxCalls = config.maxCalls ?? 500;
31
-
32
- config.tokens.forEach(({ token, name }) => {
33
- this.createWorkers(token, name);
34
- });
35
- }
36
-
37
- getLastUsedWorkerName(): string {
38
- const baseName = this.tokenNames.get(this.lastUsedToken) ?? "Unknown";
39
- return `${baseName}-${this.lastUsedWorkerIndex}`;
40
- }
41
-
42
- getStats(): TokenStats[] {
43
- return [...this.workers.keys()].map((token) => ({
44
- token,
45
- name: this.tokenNames.get(token) ?? "Unknown",
46
- requests: this.requestCounts.get(token) ?? 0,
47
- active: !this.quotaExhausted.has(token),
48
- }));
49
- }
50
-
51
- private tokenRrIndex = 0;
52
- private workerRrIndexes = new Map<string, number>();
53
-
54
- selectWorker(): Worker | null {
55
- const availableTokens = [...this.workers.entries()].filter(
56
- ([token]) => !this.quotaExhausted.has(token),
57
- );
58
-
59
- if (availableTokens.length === 0) return null;
60
-
61
- // 1단계: 토큰별 총 큐 depth가 가장 낮은 토큰 선택
62
- const tokenDepths = availableTokens.map(([token, workers]) => ({
63
- token,
64
- workers,
65
- totalDepth: workers.reduce((sum, w) => sum + w.getQueueDepth(), 0),
66
- }));
67
- const minTokenDepth = Math.min(...tokenDepths.map((t) => t.totalDepth));
68
- const idleTokens = tokenDepths.filter((t) => t.totalDepth === minTokenDepth);
69
- const selected = idleTokens[this.tokenRrIndex % idleTokens.length]!;
70
- this.tokenRrIndex++;
71
-
72
- // 2단계: 토큰 내에서 least-depth 워커, 동률이면 round-robin
73
- const minDepth = Math.min(...selected.workers.map((w) => w.getQueueDepth()));
74
- const idleWorkers = selected.workers.filter((w) => w.getQueueDepth() === minDepth);
75
- const wrIdx = this.workerRrIndexes.get(selected.token) ?? 0;
76
- const picked = idleWorkers[wrIdx % idleWorkers.length]!;
77
- this.workerRrIndexes.set(selected.token, wrIdx + 1);
78
-
79
- return picked;
80
- }
81
-
82
- async query(input: QueryInput, timeoutMs?: number): Promise<CliResult> {
83
- const triedTokens = new Set<string>();
84
-
85
- while (true) {
86
- const worker = this.selectWorker();
87
- if (!worker) {
88
- throw new QuotaError("All tokens exhausted");
89
- }
90
-
91
- if (triedTokens.has(worker.tokenId)) {
92
- throw new QuotaError("All tokens exhausted");
93
- }
94
-
95
- const workerName = this.tokenNames.get(worker.tokenId) ?? "Unknown";
96
- const tokenWorkers = this.workers.get(worker.tokenId);
97
- const workerIdx = tokenWorkers ? tokenWorkers.indexOf(worker) + 1 : 0;
98
- console.log(`[qgrid] → ${workerName}-${workerIdx} (queue: ${worker.getQueueDepth()})`);
99
-
100
- try {
101
- const result = await worker.query(input, timeoutMs);
102
- this.lastUsedToken = worker.tokenId;
103
- this.lastUsedWorkerIndex = workerIdx;
104
- this.requestCounts.set(worker.tokenId, (this.requestCounts.get(worker.tokenId) ?? 0) + 1);
105
-
106
- return result;
107
- } catch (err) {
108
- if (err instanceof QuotaError) {
109
- this.quotaExhausted.add(worker.tokenId);
110
- triedTokens.add(worker.tokenId);
111
- continue;
112
- }
113
- throw err;
114
- }
115
- }
116
- }
117
-
118
- kill(): void {
119
- [...this.workers.values()].flat().forEach((w) => {
120
- w.kill();
121
- });
122
- }
123
-
124
- createWorkers(token: string, name: string): void {
125
- if (this.workers.has(token)) return;
126
-
127
- this.tokenNames.set(token, name);
128
- const workerConfig: WorkerConfig = {
129
- token,
130
- model: this.model,
131
- timeout: this.timeout,
132
- cwd: this.cwd,
133
- maxCalls: this.maxCalls,
134
- };
135
- const workers = Array.from({ length: this.size }, () => new Worker(workerConfig));
136
- this.workers.set(token, workers);
137
- this.requestCounts.set(token, 0);
138
- }
139
-
140
- destroyWorkers(token: string): void {
141
- const workers = this.workers.get(token);
142
- if (!workers) return;
143
-
144
- workers.forEach((w) => {
145
- w.kill();
146
- });
147
- this.workers.delete(token);
148
- this.tokenNames.delete(token);
149
- this.quotaExhausted.delete(token);
150
- this.requestCounts.delete(token);
151
- }
152
- }
153
-
154
- // Pool 싱글턴 관리
155
- let pool: ClaudePool | null = null;
156
-
157
- export function initPool(tokens: { token: string; name: string }[]): ClaudePool {
158
- pool = new ClaudePool({ tokens });
159
- return pool;
160
- }
161
-
162
- export function getPool(): ClaudePool {
163
- if (!pool) throw new Error("Pool not initialized. Call initPool() first.");
164
- return pool;
165
- }