lody 0.85.0 → 0.86.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.
package/dist/codex-acp.js CHANGED
@@ -3,5 +3,5 @@
3
3
  console.error("CODEX_PATH is required for the bundled Codex ACP adapter.");
4
4
  process.exit(1);
5
5
  }
6
- await import("./chunks/index-ddMw90Gr.js");
6
+ await import("./chunks/index-Dd0J9MIK.js");
7
7
  })();
@@ -15,6 +15,9 @@ const inject = [
15
15
  "sessionPersistence",
16
16
  "sessionQuery"
17
17
  ];
18
+ const LODY_CAPABILITIES = {
19
+ compaction: { version: 1 }
20
+ };
18
21
  const MODEL_CONFIG_ID = "model";
19
22
  const MODE_CONFIG_ID = "mode";
20
23
  const REASONING_EFFORT_CONFIG_ID = "reasoning_effort";
@@ -518,6 +521,40 @@ function apply(ctx, rawConfig) {
518
521
  });
519
522
  }
520
523
  }
524
+ } else if (event.type === "compaction/start" && event.data.compactionId) {
525
+ const activity = {
526
+ version: 1,
527
+ kind: "context_compaction",
528
+ automatic: event.data.turn !== null
529
+ };
530
+ notify({
531
+ sessionId: record.agent.session.id,
532
+ update: {
533
+ sessionUpdate: "tool_call",
534
+ toolCallId: `context-compaction:${event.data.compactionId}`,
535
+ title: "Compacting context",
536
+ kind: "think",
537
+ status: "in_progress",
538
+ _meta: { lody: { activity } }
539
+ }
540
+ });
541
+ } else if (event.type === "compaction/end" && event.data.compactionId) {
542
+ const activity = {
543
+ version: 1,
544
+ kind: "context_compaction",
545
+ automatic: event.data.turn !== null,
546
+ ...event.data.error ? { failureReason: event.data.error } : {}
547
+ };
548
+ notify({
549
+ sessionId: record.agent.session.id,
550
+ update: {
551
+ sessionUpdate: "tool_call_update",
552
+ toolCallId: `context-compaction:${event.data.compactionId}`,
553
+ title: event.data.error ? "Context compaction failed" : "Context compacted",
554
+ status: event.data.error ? "failed" : "completed",
555
+ _meta: { lody: { activity } }
556
+ }
557
+ });
521
558
  }
522
559
  } finally {
523
560
  const inflight = record.inflight;
@@ -616,7 +653,8 @@ function apply(ctx, rawConfig) {
616
653
  embeddedContext: false
617
654
  },
618
655
  mcpCapabilities: { http: true },
619
- sessionCapabilities: { close: {} }
656
+ sessionCapabilities: { close: {} },
657
+ _meta: { lody: LODY_CAPABILITIES }
620
658
  },
621
659
  authMethods: []
622
660
  };
@@ -1,6 +1,6 @@
1
1
  import { opendir } from "node:fs/promises";
2
2
  import path__default from "node:path";
3
- import { x as buildCodeCollabFileIndexState, D as scanGitDirectoryEntries, F as joinWorkspacePath, G as isInsideGitWorktree, A as computeAllChanges, H as pathSegmentComparisonKey, E as closeDirectoryQuietly } from "./chunks/directory-handle-OPjjCl3O.js";
3
+ import { z as buildCodeCollabFileIndexState, F as scanGitDirectoryEntries, H as joinWorkspacePath, I as isInsideGitWorktree, D as computeAllChanges, J as pathSegmentComparisonKey, G as closeDirectoryQuietly } from "./chunks/directory-handle-Bp47LF5g.js";
4
4
  import "./chunks/schemas-Du3qLZPS.js";
5
5
  import "node:child_process";
6
6
  import "node:util";
package/dist/grok-acp.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { createInterface } from "node:readline";
2
- import { r as runtimeManifest } from "./chunks/runtime-manifest-Fr5DEH54.js";
2
+ import { r as runtimeManifest } from "./chunks/runtime-manifest-ByWJfM07.js";
3
+ import { L as LODY_EXTENSION_METHODS } from "./chunks/methods-CvEDMahD.js";
3
4
  import { spawn } from "node:child_process";
4
5
  const contract = runtimeManifest.privateWireContract;
5
6
  const PERMISSION_MODES = {
@@ -16,12 +17,17 @@ const INTERACTION_FROM_RUNTIME = { default: "agent", plan: "plan", ask: "plan" }
16
17
  const LEGACY_INTERACTION_ALIASES = { ask: "plan" };
17
18
  const INTERNAL_REQUESTS = {
18
19
  context: { contractKey: "sessionInfoRequest", params: (sessionId) => ({ sessionId }) },
19
- billing: { contractKey: "billingRequest", params: () => ({}) }
20
+ billing: { contractKey: "billingRequest", params: () => ({}) },
21
+ rateLimits: { contractKey: "billingRequest", params: () => ({}) }
20
22
  };
21
23
  const USD_TICKS_PER_USD = 1e10;
22
24
  const MAX_TRACKED_PROMPTS = 256;
23
- const FIVE_HOUR_WINDOW_MINS = 5 * 60;
24
- const SEVEN_DAY_WINDOW_MINS = 7 * 24 * 60;
25
+ const GROK_LODY_CAPABILITIES = {
26
+ usage: { version: 1 },
27
+ rateLimits: { version: 1, query: true }
28
+ };
29
+ const SEVEN_DAY_WINDOW_SECONDS = 7 * 24 * 60 * 60;
30
+ const GROK_MODEL_SNAPSHOT_SETTLE_TIMEOUT_MS = 2e3;
25
31
  function logicalExtensionMethod(method) {
26
32
  return typeof method === "string" && method.startsWith("_") ? method.slice(1) : method;
27
33
  }
@@ -31,6 +37,9 @@ function wireExtensionMethod(method) {
31
37
  function extensionNotification(contractKey, params) {
32
38
  return { jsonrpc: "2.0", method: wireExtensionMethod(contract[contractKey]), params };
33
39
  }
40
+ function lodyNotification(method, params) {
41
+ return { jsonrpc: "2.0", method, params };
42
+ }
34
43
  function optionalNonnegativeNumber(value) {
35
44
  return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : void 0;
36
45
  }
@@ -53,20 +62,18 @@ function billingUsagePercent(config, period) {
53
62
  if (hasOfficialZeroUsage) return 0;
54
63
  return void 0;
55
64
  }
56
- function rateLimitsPayload(planName, window) {
57
- const isFiveHour = window?.windowDurationMins === FIVE_HOUR_WINDOW_MINS;
58
- const isSevenDay = window?.windowDurationMins === SEVEN_DAY_WINDOW_MINS;
65
+ function rateLimitsPayload(planName, window, now = Date.now()) {
59
66
  return {
60
- schemaVersion: 2,
61
- planName,
62
- limitName: "Grok Build",
63
- limitId: "grok",
64
- windows: window ? [window] : [],
65
- fiveHour: isFiveHour ? window.usedPercent : null,
66
- sevenDay: isSevenDay ? window.usedPercent : null,
67
- fiveHourResetAt: isFiveHour ? window.resetsAt : null,
68
- sevenDayResetAt: isSevenDay ? window.resetsAt : null,
69
- ...window ? {} : { apiUnavailable: true }
67
+ rateLimits: window ? [
68
+ {
69
+ limitId: "grok",
70
+ scope: { providerId: "grok" },
71
+ planName,
72
+ limitName: "Grok Build",
73
+ windows: [window]
74
+ }
75
+ ] : [],
76
+ fetchedAtEpochSeconds: Math.floor(now / 1e3)
70
77
  };
71
78
  }
72
79
  function normalizeUsageModel(usage, usageIsIncomplete) {
@@ -105,9 +112,9 @@ function normalizePromptUsage(promptUsage) {
105
112
  ...Object.keys(modelUsage).length ? { modelUsage } : {}
106
113
  };
107
114
  }
108
- function usageNotification(promptUsage) {
115
+ function usageNotification(sessionId, promptUsage) {
109
116
  const params = normalizePromptUsage(promptUsage);
110
- return params && extensionNotification("lodyUsageNotification", params);
117
+ return params && lodyNotification(LODY_EXTENSION_METHODS.sessionUsageUpdate, { sessionId, ...params });
111
118
  }
112
119
  function normalizeBillingRateLimits(billing) {
113
120
  if (!billing || typeof billing !== "object") return void 0;
@@ -121,20 +128,25 @@ function normalizeBillingRateLimits(billing) {
121
128
  const end = period?.end ?? period?.billingPeriodEnd ?? config.billingPeriodEnd;
122
129
  const startMs = typeof start === "string" ? Date.parse(start) : Number.NaN;
123
130
  const endMs = typeof end === "string" ? Date.parse(end) : Number.NaN;
124
- const measuredWindowDurationMins = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs ? Math.round((endMs - startMs) / 6e4) : null;
125
- const windowDurationMins = period?.type === contract.weeklyUsagePeriodType ? SEVEN_DAY_WINDOW_MINS : measuredWindowDurationMins;
126
- const resetsAt = Number.isFinite(endMs) ? endMs : null;
131
+ const measuredWindowDurationSeconds = Number.isFinite(startMs) && Number.isFinite(endMs) && endMs > startMs ? Math.round((endMs - startMs) / 1e3) : null;
132
+ const windowDurationSeconds = period?.type === contract.weeklyUsagePeriodType ? SEVEN_DAY_WINDOW_SECONDS : measuredWindowDurationSeconds;
133
+ const resetsAtEpochSeconds = Number.isFinite(endMs) ? Math.floor(endMs / 1e3) : null;
127
134
  const tier = billing.subscriptionTier ?? billing.subscription_tier;
128
135
  const planName = typeof tier === "string" && tier.trim() ? tier : null;
129
136
  if (usedPercent === void 0) {
130
- if (!planName && windowDurationMins === null && resetsAt === null) return void 0;
137
+ if (!planName && windowDurationSeconds === null && resetsAtEpochSeconds === null)
138
+ return void 0;
131
139
  return rateLimitsPayload(planName, null);
132
140
  }
133
- return rateLimitsPayload(planName, { usedPercent, windowDurationMins, resetsAt });
141
+ return rateLimitsPayload(planName, {
142
+ usedPercent,
143
+ windowDurationSeconds,
144
+ resetsAtEpochSeconds
145
+ });
134
146
  }
135
147
  function rateLimitsNotification(billing) {
136
148
  const params = normalizeBillingRateLimits(billing);
137
- return params && extensionNotification("lodyRateLimitsNotification", params);
149
+ return params && lodyNotification(LODY_EXTENSION_METHODS.rateLimitsUpdate, params);
138
150
  }
139
151
  function contextUsageNotification(sessionId, context) {
140
152
  const size = nonnegativeNumber(context?.total);
@@ -184,6 +196,24 @@ function readReasoningEfforts(model) {
184
196
  if (!Array.isArray(raw)) return [];
185
197
  return raw.map((item) => typeof item === "string" ? item : item?.id ?? item?.reasoningEffort).filter((item) => typeof item === "string");
186
198
  }
199
+ function readModelSnapshot(params) {
200
+ if (!params || typeof params !== "object" || !Array.isArray(params.availableModels)) {
201
+ return void 0;
202
+ }
203
+ if (params.availableModels.length === 0 || params.availableModels.some(
204
+ (model) => !model || typeof model !== "object" || typeof model.modelId !== "string"
205
+ )) {
206
+ return void 0;
207
+ }
208
+ const currentModelId = params.currentModelId;
209
+ if (typeof currentModelId !== "string" || !params.availableModels.some((model) => model.modelId === currentModelId)) {
210
+ return void 0;
211
+ }
212
+ return {
213
+ currentModelId,
214
+ availableModels: params.availableModels
215
+ };
216
+ }
187
217
  function selectOption(id, name, description, currentValue, options, category) {
188
218
  return {
189
219
  id,
@@ -240,17 +270,107 @@ function translateSessionStart(message) {
240
270
  };
241
271
  }
242
272
  class GrokAcpCompatibilityProxy {
243
- constructor() {
273
+ constructor({ deferSessionResponseUntilModelSnapshot = false } = {}) {
244
274
  this.sessions = /* @__PURE__ */ new Map();
245
275
  this.pending = /* @__PURE__ */ new Map();
276
+ this.pendingSessionResponses = /* @__PURE__ */ new Map();
277
+ this.clientVisibleSessions = /* @__PURE__ */ new Set();
278
+ this.latestModelSnapshot = void 0;
279
+ this.latestModelSnapshotFingerprint = void 0;
280
+ this.deferSessionResponseUntilModelSnapshot = deferSessionResponseUntilModelSnapshot;
246
281
  this.permissionModes = /* @__PURE__ */ new Map();
247
282
  this.nextInternalRequestId = Number.MAX_SAFE_INTEGER;
248
283
  }
249
- internalRequest(kind, sessionId) {
284
+ applyModelSnapshot(state, snapshot) {
285
+ const previousModelId = state.currentModelId;
286
+ const previousReasoningEffort = state.reasoningEffort;
287
+ const currentModel = snapshot.availableModels.find(
288
+ (model) => model.modelId === snapshot.currentModelId
289
+ );
290
+ const reasoningEfforts = readReasoningEfforts(currentModel);
291
+ const metadataReasoningEffort = currentModel?._meta?.reasoningEffort ?? currentModel?._meta?.reasoning_effort;
292
+ state.currentModelId = snapshot.currentModelId;
293
+ state.models = snapshot.availableModels;
294
+ state.reasoningEfforts = reasoningEfforts;
295
+ state.reasoningEffort = previousModelId === snapshot.currentModelId && reasoningEfforts.includes(previousReasoningEffort) ? previousReasoningEffort : typeof metadataReasoningEffort === "string" && reasoningEfforts.includes(metadataReasoningEffort) ? metadataReasoningEffort : reasoningEfforts[0];
296
+ }
297
+ sessionResponseWithState(message, state) {
298
+ const models = typeof state.currentModelId === "string" && state.models.length > 0 ? {
299
+ currentModelId: state.currentModelId,
300
+ availableModels: state.models
301
+ } : message.result?.models;
302
+ return {
303
+ ...message,
304
+ result: {
305
+ ...message.result,
306
+ ...models ? { models } : {},
307
+ configOptions: this.configOptions(state)
308
+ }
309
+ };
310
+ }
311
+ exposePendingSessionResponse(id) {
312
+ const pending = this.pendingSessionResponses.get(id);
313
+ if (!pending) return { toRuntime: [], toClient: [] };
314
+ this.pendingSessionResponses.delete(id);
315
+ const state = this.sessions.get(pending.sessionId);
316
+ if (!state) return { toRuntime: [], toClient: [pending.message] };
317
+ this.clientVisibleSessions.add(pending.sessionId);
318
+ return {
319
+ toRuntime: [],
320
+ toClient: [this.sessionResponseWithState(pending.message, state)]
321
+ };
322
+ }
323
+ flushPendingSessionResponse(id) {
324
+ return this.exposePendingSessionResponse(id);
325
+ }
326
+ handleModelSnapshot(message) {
327
+ const snapshot = readModelSnapshot(message.params);
328
+ if (!snapshot) return { toRuntime: [], toClient: [message] };
329
+ const fingerprint = JSON.stringify(snapshot);
330
+ if (fingerprint === this.latestModelSnapshotFingerprint) {
331
+ return { toRuntime: [], toClient: [] };
332
+ }
333
+ this.latestModelSnapshot = snapshot;
334
+ this.latestModelSnapshotFingerprint = fingerprint;
335
+ for (const state of this.sessions.values()) {
336
+ this.applyModelSnapshot(state, snapshot);
337
+ }
338
+ const toClient = [];
339
+ const pendingSessionIds = /* @__PURE__ */ new Set();
340
+ const settledSessionResponseIds = [];
341
+ for (const id of [...this.pendingSessionResponses.keys()]) {
342
+ const pending = this.pendingSessionResponses.get(id);
343
+ if (pending) pendingSessionIds.add(pending.sessionId);
344
+ toClient.push(...this.exposePendingSessionResponse(id).toClient);
345
+ settledSessionResponseIds.push(id);
346
+ }
347
+ for (const sessionId of this.clientVisibleSessions) {
348
+ if (pendingSessionIds.has(sessionId)) continue;
349
+ const state = this.sessions.get(sessionId);
350
+ if (!state) continue;
351
+ toClient.push({
352
+ jsonrpc: "2.0",
353
+ method: "session/update",
354
+ params: {
355
+ sessionId,
356
+ update: {
357
+ sessionUpdate: "config_option_update",
358
+ configOptions: this.configOptions(state)
359
+ }
360
+ }
361
+ });
362
+ }
363
+ return {
364
+ toRuntime: [],
365
+ toClient,
366
+ ...settledSessionResponseIds.length > 0 ? { settledSessionResponseIds } : {}
367
+ };
368
+ }
369
+ internalRequest(kind, sessionId, responseId) {
250
370
  while (this.pending.has(this.nextInternalRequestId)) this.nextInternalRequestId -= 1;
251
371
  const id = this.nextInternalRequestId;
252
372
  this.nextInternalRequestId -= 1;
253
- this.pending.set(id, { kind, sessionId });
373
+ this.pending.set(id, { kind, sessionId, responseId });
254
374
  const { contractKey, params } = INTERNAL_REQUESTS[kind];
255
375
  return {
256
376
  jsonrpc: "2.0",
@@ -267,7 +387,7 @@ class GrokAcpCompatibilityProxy {
267
387
  return this.usageRefreshRequests(state.sessionId);
268
388
  }
269
389
  usageForPrompt(state, promptId, promptUsage) {
270
- const notification = usageNotification(promptUsage);
390
+ const notification = usageNotification(state.sessionId, promptUsage);
271
391
  if (!notification) return void 0;
272
392
  if (!rememberPrompt(state.usagePromptIds, promptId)) return void 0;
273
393
  return notification;
@@ -275,6 +395,16 @@ class GrokAcpCompatibilityProxy {
275
395
  handleClient(message) {
276
396
  if (!message || typeof message !== "object") return { toRuntime: [message], toClient: [] };
277
397
  const params = message.params ?? {};
398
+ if (message.method === "initialize" && message.id !== void 0) {
399
+ this.pending.set(message.id, { kind: "initialize" });
400
+ return { toRuntime: [message], toClient: [] };
401
+ }
402
+ if (message.method === LODY_EXTENSION_METHODS.rateLimitsGet) {
403
+ return {
404
+ toRuntime: [this.internalRequest("rateLimits", void 0, message.id)],
405
+ toClient: []
406
+ };
407
+ }
278
408
  if (message.method === "session/new" || message.method === "session/load" || message.method === "session/resume" || message.method === "session/fork") {
279
409
  const clientIdentifier = params._meta?.clientIdentifier;
280
410
  const translated2 = translateSessionStart(message);
@@ -404,27 +534,68 @@ class GrokAcpCompatibilityProxy {
404
534
  };
405
535
  }
406
536
  if (pending.kind === "billing") {
407
- const notification = message.error ? extensionNotification("lodyRateLimitsNotification", rateLimitsPayload(null, null)) : rateLimitsNotification(unwrapExtensionResult(message.result));
537
+ const notification = message.error ? lodyNotification(LODY_EXTENSION_METHODS.rateLimitsUpdate, rateLimitsPayload(null, null)) : rateLimitsNotification(unwrapExtensionResult(message.result));
408
538
  return {
409
539
  toRuntime: [],
410
540
  toClient: notification ? [notification] : []
411
541
  };
412
542
  }
543
+ if (pending.kind === "rateLimits") {
544
+ const result = message.error ? rateLimitsPayload(null, null) : normalizeBillingRateLimits(unwrapExtensionResult(message.result)) ?? rateLimitsPayload(null, null);
545
+ return {
546
+ toRuntime: [],
547
+ toClient: [{ jsonrpc: "2.0", id: pending.responseId, result }]
548
+ };
549
+ }
550
+ if (pending.kind === "initialize") {
551
+ if (message.error) return { toRuntime: [], toClient: [message] };
552
+ const result = message.result ?? {};
553
+ const agentCapabilities = result.agentCapabilities ?? {};
554
+ return {
555
+ toRuntime: [],
556
+ toClient: [
557
+ {
558
+ ...message,
559
+ result: {
560
+ ...result,
561
+ agentCapabilities: {
562
+ ...agentCapabilities,
563
+ _meta: {
564
+ ...agentCapabilities._meta ?? {},
565
+ lody: GROK_LODY_CAPABILITIES
566
+ }
567
+ }
568
+ }
569
+ }
570
+ ]
571
+ };
572
+ }
413
573
  if (message.error) return { toRuntime: [], toClient: [message] };
414
574
  if (pending.kind === "session") {
415
575
  const result = message.result ?? {};
416
576
  const sessionId = result.sessionId ?? pending.sessionId;
417
577
  if (!sessionId) return { toRuntime: [], toClient: [message] };
418
578
  const state2 = this.stateFromSessionResponse(sessionId, pending.clientIdentifier, result);
579
+ if (this.latestModelSnapshot) {
580
+ this.applyModelSnapshot(state2, this.latestModelSnapshot);
581
+ }
419
582
  this.sessions.set(sessionId, state2);
583
+ const sessionResponse = this.sessionResponseWithState(message, state2);
584
+ if (this.deferSessionResponseUntilModelSnapshot && !this.latestModelSnapshot && message.id !== void 0) {
585
+ this.pendingSessionResponses.set(message.id, {
586
+ message: sessionResponse,
587
+ sessionId
588
+ });
589
+ return {
590
+ toRuntime: this.usageRefreshRequests(sessionId),
591
+ toClient: [],
592
+ deferredSessionResponseIds: [message.id]
593
+ };
594
+ }
595
+ this.clientVisibleSessions.add(sessionId);
420
596
  return {
421
597
  toRuntime: this.usageRefreshRequests(sessionId),
422
- toClient: [
423
- {
424
- ...message,
425
- result: { ...result, configOptions: this.configOptions(state2) }
426
- }
427
- ]
598
+ toClient: [sessionResponse]
428
599
  };
429
600
  }
430
601
  if (pending.kind === "prompt") {
@@ -456,11 +627,14 @@ class GrokAcpCompatibilityProxy {
456
627
  }
457
628
  handleRuntimeMethod(message) {
458
629
  const passthrough = { toRuntime: [], toClient: [message] };
630
+ const logicalMethod = logicalExtensionMethod(message.method);
631
+ if (logicalMethod === contract.modelsUpdateNotification) {
632
+ return this.handleModelSnapshot(message);
633
+ }
459
634
  const sessionId = message.params?.sessionId;
460
635
  const update = message.params?.update;
461
636
  const state = this.sessions.get(sessionId);
462
637
  if (!state) return passthrough;
463
- const logicalMethod = logicalExtensionMethod(message.method);
464
638
  if (logicalMethod === contract.sessionUpdateNotification && update?.sessionUpdate === contract.turnCompletedUpdate) {
465
639
  if (message.params?._meta?.isReplay === true) return passthrough;
466
640
  const promptId = update.prompt_id ?? update.promptId;
@@ -611,16 +785,36 @@ if (!grokPath) {
611
785
  process.exit(1);
612
786
  }
613
787
  const child = spawnGrokRuntime(grokPath);
614
- const proxy = new GrokAcpCompatibilityProxy();
788
+ const proxy = new GrokAcpCompatibilityProxy({
789
+ deferSessionResponseUntilModelSnapshot: true
790
+ });
791
+ const deferredResponseTimers = /* @__PURE__ */ new Map();
615
792
  function write(stream, message) {
616
793
  stream.write(`${JSON.stringify(message)}
617
794
  `);
618
795
  }
796
+ function dispatch(output) {
797
+ for (const id of output.settledSessionResponseIds ?? []) {
798
+ const timer = deferredResponseTimers.get(id);
799
+ if (timer) clearTimeout(timer);
800
+ deferredResponseTimers.delete(id);
801
+ }
802
+ for (const message of output.toRuntime) write(child.stdin, message);
803
+ for (const message of output.toClient) write(process.stdout, message);
804
+ for (const id of output.deferredSessionResponseIds ?? []) {
805
+ const existingTimer = deferredResponseTimers.get(id);
806
+ if (existingTimer) clearTimeout(existingTimer);
807
+ const timer = setTimeout(() => {
808
+ deferredResponseTimers.delete(id);
809
+ dispatch(proxy.flushPendingSessionResponse(id));
810
+ }, GROK_MODEL_SNAPSHOT_SETTLE_TIMEOUT_MS);
811
+ deferredResponseTimers.set(id, timer);
812
+ timer.unref();
813
+ }
814
+ }
619
815
  createInterface({ input: process.stdin }).on("line", (line) => {
620
816
  try {
621
- const output = proxy.handleClient(JSON.parse(line));
622
- for (const message of output.toRuntime) write(child.stdin, message);
623
- for (const message of output.toClient) write(process.stdout, message);
817
+ dispatch(proxy.handleClient(JSON.parse(line)));
624
818
  } catch (error) {
625
819
  console.error(
626
820
  `Invalid ACP client message: ${error instanceof Error ? error.message : String(error)}`
@@ -629,9 +823,7 @@ createInterface({ input: process.stdin }).on("line", (line) => {
629
823
  });
630
824
  createInterface({ input: child.stdout }).on("line", (line) => {
631
825
  try {
632
- const output = proxy.handleRuntime(JSON.parse(line));
633
- for (const message of output.toRuntime) write(child.stdin, message);
634
- for (const message of output.toClient) write(process.stdout, message);
826
+ dispatch(proxy.handleRuntime(JSON.parse(line)));
635
827
  } catch (error) {
636
828
  console.error(
637
829
  `Invalid Grok runtime message: ${error instanceof Error ? error.message : String(error)}`