@noopolis/daimon 0.2.4 → 0.2.5

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.
@@ -0,0 +1,29 @@
1
+ import type { ServerResponse } from "node:http";
2
+ import type { GrokBrokerTurnMeter } from "./grokBrokerTurnMeter.js";
3
+ type Admission = ReturnType<GrokBrokerTurnMeter["admit"]>;
4
+ /** Admitted (index and upstream signal) or refused by a limit: never `busy`. */
5
+ export type GrokBrokerSettledAdmission = Exclude<Admission, Readonly<{
6
+ busy: true;
7
+ }>>;
8
+ /**
9
+ * Admits one request to a meter's single upstream lane, waiting its turn
10
+ * instead of refusing it while another request is in flight.
11
+ *
12
+ * A refusal there is not a spend control — the waiting request is admitted
13
+ * afresh, every limit re-checked against the total the in-flight answer just
14
+ * reported into — but it is fatal to the turn: the worker runs with
15
+ * `max_retries = 0`, so one 429 `turn request in flight` ends its turn. That is
16
+ * what killed Grok training pilots 52–54 (`grokBrokerTurnMeter.ts`): the proxy
17
+ * received two copies of one logically sequential request and refused the copy
18
+ * the worker was reading.
19
+ *
20
+ * - `gone`: the client closed its connection while waiting. The request is
21
+ * never admitted, counted or forwarded; nobody is left to read an answer.
22
+ * - `revoked`: `current()` turned false while waiting (the turn ended and was
23
+ * revoked). The caller refuses it exactly as a request that arrived late.
24
+ */
25
+ export declare function admitWhenIdle(meter: GrokBrokerTurnMeter, response: ServerResponse, options: Readonly<{
26
+ keepFinal?: boolean;
27
+ }>, current?: () => boolean): Promise<GrokBrokerSettledAdmission | "gone" | "revoked">;
28
+ export {};
29
+ //# sourceMappingURL=grokBrokerAdmission.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"grokBrokerAdmission.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerAdmission.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAEhD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAEpE,KAAK,SAAS,GAAG,UAAU,CAAC,mBAAmB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC1D,gFAAgF;AAChF,MAAM,MAAM,0BAA0B,GAAG,OAAO,CAAC,SAAS,EAAE,QAAQ,CAAC;IAAE,IAAI,EAAE,IAAI,CAAA;CAAE,CAAC,CAAC,CAAC;AAEtF;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,mBAAmB,EAAE,QAAQ,EAAE,cAAc,EAAE,OAAO,EAAE,QAAQ,CAAC;IAAE,SAAS,CAAC,EAAE,OAAO,CAAA;CAAE,CAAC,EAAE,OAAO,GAAE,MAAM,OAAoB,GACpI,OAAO,CAAC,0BAA0B,GAAG,MAAM,GAAG,SAAS,CAAC,CAe1D"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Admits one request to a meter's single upstream lane, waiting its turn
3
+ * instead of refusing it while another request is in flight.
4
+ *
5
+ * A refusal there is not a spend control — the waiting request is admitted
6
+ * afresh, every limit re-checked against the total the in-flight answer just
7
+ * reported into — but it is fatal to the turn: the worker runs with
8
+ * `max_retries = 0`, so one 429 `turn request in flight` ends its turn. That is
9
+ * what killed Grok training pilots 52–54 (`grokBrokerTurnMeter.ts`): the proxy
10
+ * received two copies of one logically sequential request and refused the copy
11
+ * the worker was reading.
12
+ *
13
+ * - `gone`: the client closed its connection while waiting. The request is
14
+ * never admitted, counted or forwarded; nobody is left to read an answer.
15
+ * - `revoked`: `current()` turned false while waiting (the turn ended and was
16
+ * revoked). The caller refuses it exactly as a request that arrived late.
17
+ */
18
+ export async function admitWhenIdle(meter, response, options, current = () => true) {
19
+ let admission = meter.admit(options);
20
+ if (!("busy" in admission))
21
+ return admission;
22
+ let gone = false, onClose;
23
+ const closed = new Promise((resolve) => { onClose = () => { gone = true; resolve(); }; });
24
+ response.once("close", onClose);
25
+ try {
26
+ while ("busy" in admission) {
27
+ await Promise.race([meter.whenIdle(), closed]);
28
+ if (gone || response.destroyed)
29
+ return "gone";
30
+ if (!current())
31
+ return "revoked";
32
+ admission = meter.admit(options);
33
+ }
34
+ return admission;
35
+ }
36
+ finally {
37
+ response.off("close", onClose);
38
+ }
39
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAExF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,EAAmC,MAAM,6BAA6B,CAAC;AAG/G,OAAO,EAA2E,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG7I,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAkC,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5G,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,WAAW,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,qBAAqB,CAAC,CAAC,mBAAmB,EAAC,MAAM,GAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,mBAAmB,CAAC,EAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,OAAO,CAAA;CAAE,CAAC,CAAC;AACjQ,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,+BAA+B,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC;AAE3N;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,UAAU,SAAS,EACrF,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;;;mCAoC5B,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;GA5B7D;AAED,iFAAiF;AACjF,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;mCAc9F,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;EAI7D;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAMnC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IALtF;;;;OAIG;gBACkB,MAAM,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,MAAM,YAAA,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA;CACvF"}
1
+ {"version":3,"file":"grokBrokerProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerProxy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAsB,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGxF,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,WAAW,CAAC;AAIpF,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAEzE,OAAO,EAAgE,KAAK,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AACtI,OAAO,EAAE,+BAA+B,EAAmC,MAAM,6BAA6B,CAAC;AAG/G,OAAO,EAA2E,KAAK,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAG7I,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AACpE,OAAO,EAAkC,KAAK,uBAAuB,EAAE,MAAM,8BAA8B,CAAC;AAE5G,2FAA2F;AAC3F,MAAM,MAAM,mBAAmB,GAAG,QAAQ,CAAC;IACzC,MAAM,EAAE,qBAAqB,CAAC;IAC9B,KAAK,EAAE,mBAAmB,CAAC;IAC3B,KAAK,CAAC,EAAE,uBAAuB,CAAC;CACjC,CAAC,CAAC;AAEH,MAAM,MAAM,6BAA6B,GAAG,QAAQ,CAAC;IAAE,WAAW,CAAC,YAAY,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,qBAAqB,CAAC,CAAC,mBAAmB,EAAC,MAAM,GAAE,OAAO,CAAC,MAAM,CAAC,CAAC;IAAC,YAAY,CAAC,mBAAmB,CAAC,EAAC,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAAC,OAAO,CAAC,IAAI,OAAO,CAAA;CAAE,CAAC,CAAC;AACjQ,MAAM,MAAM,kBAAkB,GAAG,CAAC,OAAO,EAAE,UAAU,CAAC,OAAO,+BAA+B,CAAC,EAAE,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,UAAU,CAAA;CAAE,CAAC,CAAC,CAAC;AAE3N;;;;;;;;;;GAUG;AACH,wBAAsB,oBAAoB,CACxC,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,UAAU,SAAS,EACrF,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;;;mCAoC5B,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;GA5B7D;AAED,iFAAiF;AACjF,wBAAgB,4BAA4B,CAC1C,SAAS,EAAE,6BAA6B,EAAE,QAAQ,GAAE,kBAAoC,EACxF,MAAM,GAAE,qBAAwD,EAAE,MAAM,CAAC,EAAE,mBAAmB,EAAE,SAAS,CAAC,EAAE,mBAAmB;;mCAc9F,MAAM,SAAS,MAAM,OAAO,CAAC,IAAI,CAAC;iCACpC,MAAM;yBACd,MAAM,QAAQ,mBAAmB;uBAMnC,MAAM;qBACR,eAAe,YAAY,cAAc;EAI7D;AAED;;;;;;;;GAQG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAMnC,QAAQ,CAAC,MAAM,EAAE,MAAM;IAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM;IAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM;IALtF;;;;OAIG;gBACkB,MAAM,EAAE,MAAM,EAAW,KAAK,CAAC,EAAE,MAAM,YAAA,EAAW,MAAM,CAAC,EAAE,MAAM,YAAA;CACvF"}
@@ -1,4 +1,5 @@
1
1
  import { GrokBrokerRunLimit } from "./grokBrokerRunBudget.js";
2
+ import { admitWhenIdle } from "./grokBrokerAdmission.js";
2
3
  import { createHash } from "node:crypto";
3
4
  import { createServer } from "node:http";
4
5
  import { redactCredentialText } from "../core/credentialRedaction.js";
@@ -147,17 +148,18 @@ async function serve(request, response, authority, upstream, capabilities, guard
147
148
  token = "";
148
149
  // The spend gate runs after the body is proven a real lean worker request
149
150
  // (a refused session-title body never counts) and before any upstream call.
150
- const admission = turn.meter.admit({ keepFinal: prepared.compaction === true });
151
+ // A request that meets another still in flight waits for it rather than being
152
+ // refused: one 429 ends the worker's turn (`grokBrokerAdmission.ts`).
153
+ const admission = await admitWhenIdle(turn.meter, response, { keepFinal: prepared.compaction === true }, () => turns.get(scope.turnId) === turn && guards.has(scope.turnId));
154
+ if (admission === "gone")
155
+ return;
156
+ if (admission === "revoked")
157
+ throw new GrokBrokerProxyRefusal("no_active_turn");
151
158
  if ("refused" in admission) {
152
159
  response.writeHead(429, { "content-type": "application/json", "cache-control": "no-store" });
153
160
  response.end(JSON.stringify({ error: "turn limit reached", limit: admission.refused }));
154
161
  return;
155
162
  }
156
- if ("busy" in admission) {
157
- response.writeHead(429, { "content-type": "application/json", "cache-control": "no-store" });
158
- response.end('{"error":"turn request in flight"}');
159
- return;
160
- }
161
163
  settle = (usage, toolCalls, finalReply) => { turn.meter.settle(admission.index, usage, body.byteLength, toolCalls, finalReply); settle = undefined; };
162
164
  let result = await upstream(prepared, admission.signal);
163
165
  if (result.status === 401) {
@@ -63,13 +63,20 @@ export declare const grokCeilingTokens: (usage: EngineBrokerTurnUsage) => number
63
63
  *
64
64
  * The token bound is only a bound if no request can be admitted on a total
65
65
  * that an in-flight request has not yet reported into. So a turn has at most
66
- * ONE upstream request in flight: a second request arriving before the first
67
- * settled is refused (`busy`, HTTP 429) without being counted or tripping a
68
- * limit. Grok's headless loop is sequential — every live capture (P1
69
- * live-round1, P2 live) shows each request ending before the next starts — so
70
- * this refuses only a worker that is not behaving like Grok. Tripping a limit
71
- * (including the broker's timer) aborts that in-flight upstream call through
72
- * its own `AbortSignal` rather than letting it run to completion.
66
+ * ONE upstream request in flight: {@link admit} answers `busy` for a second
67
+ * request while the first is unsettled, without counting it or tripping a
68
+ * limit, and the proxy then waits on {@link whenIdle} and admits it afresh —
69
+ * every limit re-checked against a total that now includes the first answer.
70
+ * It is never refused for arriving early: Grok's loop is sequential, but the
71
+ * proxy does not see it that way. Training pilot run54 logged one
72
+ * `inference_start` for loop 17, yet two copies of that request reached the
73
+ * proxy 4 ms apart (`provider_clock.requests` 18 against 17 admitted); the
74
+ * first was admitted and answered upstream, the second was refused 429 `turn
75
+ * request in flight`, and that refusal was the copy Grok read, so the turn
76
+ * failed at the 10-minute idle bound with its work in progress. Runs 52 and 53
77
+ * died the same way. Tripping a limit (including the broker's timer) aborts
78
+ * that in-flight upstream call through its own `AbortSignal` rather than
79
+ * letting it run to completion, and wakes every waiter into its refusal.
73
80
  */
74
81
  export declare class GrokBrokerTurnMeter {
75
82
  readonly limits: EngineBrokerTurnLimits;
@@ -82,6 +89,7 @@ export declare class GrokBrokerTurnMeter {
82
89
  private inFlight;
83
90
  private final;
84
91
  private activityAt;
92
+ private idleWaiters;
85
93
  constructor(limits: EngineBrokerTurnLimits, onLimit?: (reason: Exclude<EngineBrokerLimitReason, "none">) => void, now?: () => number);
86
94
  /**
87
95
  * Returns the request index and its upstream abort signal when admitted, the limit that refused it, or `busy` while another request is in flight.
@@ -112,6 +120,13 @@ export declare class GrokBrokerTurnMeter {
112
120
  * not exited, and report finished work whose stdout the launcher refused.
113
121
  */
114
122
  settle(index: number, usage: EngineBrokerTurnUsage | undefined, requestBytes: number, toolCalls?: readonly string[], finalReply?: string): void;
123
+ /**
124
+ * Resolves once no request is in flight, or once a limit has tripped. A
125
+ * request that met `busy` waits here and calls {@link admit} again; it may
126
+ * meet `busy` once more if another waiter was admitted first.
127
+ */
128
+ whenIdle(): Promise<void>;
129
+ private wakeIdle;
115
130
  /** The turn's final reply, while no later request has been admitted. */
116
131
  finalReply(): GrokBrokerFinalReply | undefined;
117
132
  /** Whether a model request is in flight right now. */
@@ -1 +1 @@
1
- {"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"grokBrokerTurnMeter.d.ts","sourceRoot":"","sources":["../../src/runtime/grokBrokerTurnMeter.ts"],"names":[],"mappings":"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"}
@@ -37,13 +37,20 @@ export const grokCeilingTokens = (usage) => usage.total - usage.cacheRead + Math
37
37
  *
38
38
  * The token bound is only a bound if no request can be admitted on a total
39
39
  * that an in-flight request has not yet reported into. So a turn has at most
40
- * ONE upstream request in flight: a second request arriving before the first
41
- * settled is refused (`busy`, HTTP 429) without being counted or tripping a
42
- * limit. Grok's headless loop is sequential — every live capture (P1
43
- * live-round1, P2 live) shows each request ending before the next starts — so
44
- * this refuses only a worker that is not behaving like Grok. Tripping a limit
45
- * (including the broker's timer) aborts that in-flight upstream call through
46
- * its own `AbortSignal` rather than letting it run to completion.
40
+ * ONE upstream request in flight: {@link admit} answers `busy` for a second
41
+ * request while the first is unsettled, without counting it or tripping a
42
+ * limit, and the proxy then waits on {@link whenIdle} and admits it afresh —
43
+ * every limit re-checked against a total that now includes the first answer.
44
+ * It is never refused for arriving early: Grok's loop is sequential, but the
45
+ * proxy does not see it that way. Training pilot run54 logged one
46
+ * `inference_start` for loop 17, yet two copies of that request reached the
47
+ * proxy 4 ms apart (`provider_clock.requests` 18 against 17 admitted); the
48
+ * first was admitted and answered upstream, the second was refused 429 `turn
49
+ * request in flight`, and that refusal was the copy Grok read, so the turn
50
+ * failed at the 10-minute idle bound with its work in progress. Runs 52 and 53
51
+ * died the same way. Tripping a limit (including the broker's timer) aborts
52
+ * that in-flight upstream call through its own `AbortSignal` rather than
53
+ * letting it run to completion, and wakes every waiter into its refusal.
47
54
  */
48
55
  export class GrokBrokerTurnMeter {
49
56
  limits;
@@ -56,6 +63,7 @@ export class GrokBrokerTurnMeter {
56
63
  inFlight;
57
64
  final;
58
65
  activityAt;
66
+ idleWaiters = [];
59
67
  constructor(limits, onLimit = () => undefined, now = Date.now) {
60
68
  this.limits = limits;
61
69
  this.onLimit = onLimit;
@@ -122,6 +130,25 @@ export class GrokBrokerTurnMeter {
122
130
  this.activityAt = this.now();
123
131
  if (finalReply !== undefined && toolCalls !== undefined && toolCalls.length === 0 && index === this.timings.length - 1)
124
132
  this.final = { index, text: finalReply, at: this.activityAt };
133
+ // Waiters resume asynchronously, after this settle has added the answer's tokens.
134
+ if (this.inFlight === undefined)
135
+ this.wakeIdle();
136
+ }
137
+ /**
138
+ * Resolves once no request is in flight, or once a limit has tripped. A
139
+ * request that met `busy` waits here and calls {@link admit} again; it may
140
+ * meet `busy` once more if another waiter was admitted first.
141
+ */
142
+ whenIdle() {
143
+ if (this.inFlight === undefined || this.reason !== "none")
144
+ return Promise.resolve();
145
+ return new Promise((resolve) => { this.idleWaiters.push(resolve); });
146
+ }
147
+ wakeIdle() {
148
+ const waiters = this.idleWaiters;
149
+ this.idleWaiters = [];
150
+ for (const wake of waiters)
151
+ wake();
125
152
  }
126
153
  /** The turn's final reply, while no later request has been admitted. */
127
154
  finalReply() { return this.final; }
@@ -135,6 +162,7 @@ export class GrokBrokerTurnMeter {
135
162
  return;
136
163
  this.reason = reason;
137
164
  this.abortInFlight();
165
+ this.wakeIdle();
138
166
  this.onLimit(reason);
139
167
  }
140
168
  /** Aborts the in-flight upstream call, if any (limit trip, or the broker ending the turn). */
@@ -1 +1 @@
1
- {"version":3,"file":"grokInferenceProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokInferenceProxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAGhD,OAAO,KAAK,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzK,yGAAyG;AACzG,eAAO,MAAM,8BAA8B,+BAA2B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,EAAE,SAAS,EAAE,6BAA6B,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkC1N"}
1
+ {"version":3,"file":"grokInferenceProxy.d.ts","sourceRoot":"","sources":["../../src/runtime/grokInferenceProxy.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIhD,OAAO,KAAK,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAGpE,MAAM,MAAM,uBAAuB,GAAG,QAAQ,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC;AAEzK,yGAAyG;AACzG,eAAO,MAAM,8BAA8B,+BAA2B,CAAC;AAEvE;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,uBAAuB,CAAC,KAAK,EAAE,uBAAuB,EAAE,QAAQ,EAAE,cAAc,EAAE,MAAM,EAAE,mBAAmB,EAAE,SAAS,EAAE,6BAA6B,EAAE,QAAQ,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAmC1N"}
@@ -1,5 +1,6 @@
1
1
  import { GrokBrokerRunLimit } from "./grokBrokerRunBudget.js";
2
2
  import { createHash } from "node:crypto";
3
+ import { admitWhenIdle } from "./grokBrokerAdmission.js";
3
4
  import { GrokBrokerProxyRefusal } from "./grokBrokerProxy.js";
4
5
  import { parseGrokUpstreamUsage } from "./grokBrokerTurnMeter.js";
5
6
  import { authorizeGrokInferenceProxyRequest } from "./grokInferenceProxyRequest.js";
@@ -37,11 +38,12 @@ export async function serveGrokInferenceGrant(input, response, grants, authority
37
38
  const rejectedDigest = createHash("sha256").update(token).digest("hex");
38
39
  prepared = withBearer(prepared, token);
39
40
  token = "";
40
- const admission = grant.meter.admit();
41
+ // Same lane rule as a turn: an overlapping request waits, a released grant trips its waiters into a refusal.
42
+ const admission = await admitWhenIdle(grant.meter, response, {});
43
+ if (admission === "gone" || admission === "revoked")
44
+ return;
41
45
  if ("refused" in admission)
42
46
  return json(response, 429, JSON.stringify({ error: "grant limit reached", limit: admission.refused }));
43
- if ("busy" in admission)
44
- return json(response, 429, '{"error":"grant request in flight"}');
45
47
  settle = (usage) => { settle = undefined; grants.settle(grant, admission.index, usage, input.body.byteLength); };
46
48
  let result = await upstream(prepared, admission.signal);
47
49
  if (result.status === 401) {
@@ -226,7 +226,7 @@ function moltnetTool(agent, wakeContext) {
226
226
  }
227
227
  async function connect(agent, server) {
228
228
  taskClockChildEnvironment(server.env);
229
- const client = new Client({ name: "daimon-production", version: "0.2.4" });
229
+ const client = new Client({ name: "daimon-production", version: "0.2.5" });
230
230
  const headers = server.authSecretEnv === undefined ? {} : { authorization: `Bearer ${requiredSecret(server.authSecretEnv)}` };
231
231
  const transport = server.transport === "stdio"
232
232
  ? new StdioClientTransport({ command: server.command, args: [...server.args], env: stringEnvironment(readyTaskClockEnvironment({ ...server.env, ...cliChildEnvironment([], agent.runtimeHomePath, { executablePath: server.command }) })) })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noopolis/daimon",
3
- "version": "0.2.4",
3
+ "version": "0.2.5",
4
4
  "description": "Noopolis native per-agent runtime harness built on Pi.",
5
5
  "license": "MIT",
6
6
  "type": "module",