@bman654/clodex 1.0.2 → 1.0.4

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/cli.js CHANGED
@@ -202,7 +202,7 @@ import { join } from "path";
202
202
  // package.json
203
203
  var package_default = {
204
204
  name: "@bman654/clodex",
205
- version: "1.0.2",
205
+ version: "1.0.4",
206
206
  publishConfig: {
207
207
  access: "public"
208
208
  },
@@ -7092,13 +7092,24 @@ function translateMessages(messages, npm, openAiPromptCacheBreakpoints = false)
7092
7092
  }
7093
7093
  return out;
7094
7094
  }
7095
- function stripNullInputs(input) {
7095
+ function sanitizeToolInput(input, requiredProps) {
7096
7096
  const out = {};
7097
7097
  for (const [k, v] of Object.entries(input)) {
7098
- if (v !== null) out[k] = v;
7098
+ if (v === null) continue;
7099
+ if (Array.isArray(v) && v.length === 0 && !requiredProps?.has(k)) continue;
7100
+ out[k] = v;
7099
7101
  }
7100
7102
  return out;
7101
7103
  }
7104
+ function toolRequiredProps(tools) {
7105
+ const map = /* @__PURE__ */ new Map();
7106
+ for (const [name, t] of Object.entries(tools ?? {})) {
7107
+ const schema = t.inputSchema?.jsonSchema;
7108
+ const required = Array.isArray(schema?.required) ? schema.required : [];
7109
+ map.set(name, new Set(required.filter((r) => typeof r === "string")));
7110
+ }
7111
+ return map;
7112
+ }
7102
7113
  function translateTools(anthropicTools) {
7103
7114
  if (!anthropicTools?.length) return void 0;
7104
7115
  const tools = {};
@@ -7208,13 +7219,17 @@ function forwardAbortSignal(source, target) {
7208
7219
  source.addEventListener("abort", forward, { once: true });
7209
7220
  return () => source.removeEventListener("abort", forward);
7210
7221
  }
7211
- async function writeAnthropicStream(stream, modelId, write, log12, observer) {
7222
+ async function writeAnthropicStream(stream, modelId, write, log12, observer, tools) {
7212
7223
  const messageId = "msg_" + Date.now();
7224
+ const requiredProps = toolRequiredProps(tools);
7213
7225
  let blockIndex = -1;
7214
7226
  let started = false;
7215
7227
  let openType = null;
7216
7228
  let pendingThinkingSig;
7217
7229
  const idToBlock = /* @__PURE__ */ new Map();
7230
+ const toolJsonBuffer = /* @__PURE__ */ new Map();
7231
+ const flushedTools = /* @__PURE__ */ new Set();
7232
+ let openToolId = null;
7218
7233
  let finishReason = "end_turn";
7219
7234
  let usage = {
7220
7235
  input_tokens: 0,
@@ -7254,8 +7269,20 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer) {
7254
7269
  });
7255
7270
  pendingThinkingSig = void 0;
7256
7271
  }
7272
+ if (openType === "tool" && openToolId !== null && !flushedTools.has(openToolId)) {
7273
+ const buffered = toolJsonBuffer.get(openToolId);
7274
+ if (buffered) {
7275
+ emit("content_block_delta", {
7276
+ type: "content_block_delta",
7277
+ index: blockIndex,
7278
+ delta: { type: "input_json_delta", partial_json: buffered }
7279
+ });
7280
+ }
7281
+ flushedTools.add(openToolId);
7282
+ }
7257
7283
  if (openType) emit("content_block_stop", { type: "content_block_stop", index: blockIndex });
7258
7284
  openType = null;
7285
+ openToolId = null;
7259
7286
  };
7260
7287
  const openBlock = (type, contentBlock) => {
7261
7288
  ensureStart();
@@ -7317,32 +7344,45 @@ async function writeAnthropicStream(stream, modelId, write, log12, observer) {
7317
7344
  input: {}
7318
7345
  });
7319
7346
  idToBlock.set(part.id ?? "", blockIndex);
7347
+ openToolId = part.id ?? "";
7320
7348
  break;
7321
7349
  }
7322
- case "tool-input-delta":
7323
- emit("content_block_delta", {
7324
- type: "content_block_delta",
7325
- index: idToBlock.get(part.id ?? "") ?? blockIndex,
7326
- delta: { type: "input_json_delta", partial_json: part.delta ?? part.text ?? "" }
7327
- });
7350
+ case "tool-input-delta": {
7351
+ const id = part.id ?? "";
7352
+ toolJsonBuffer.set(id, (toolJsonBuffer.get(id) ?? "") + (part.delta ?? part.text ?? ""));
7328
7353
  break;
7354
+ }
7329
7355
  case "tool-input-end":
7330
7356
  break;
7331
7357
  case "tool-call": {
7332
7358
  finishReason = "tool_use";
7333
- if (!idToBlock.has(part.toolCallId ?? "") && openType !== "tool") {
7359
+ const id = part.toolCallId ?? "";
7360
+ if (idToBlock.has(id)) {
7361
+ if (!flushedTools.has(id)) {
7362
+ const json = part.input !== void 0 && part.input !== null ? JSON.stringify(sanitizeToolInput(part.input, requiredProps.get(part.toolName ?? ""))) : toolJsonBuffer.get(id) ?? "";
7363
+ if (json) {
7364
+ emit("content_block_delta", {
7365
+ type: "content_block_delta",
7366
+ index: idToBlock.get(id) ?? blockIndex,
7367
+ delta: { type: "input_json_delta", partial_json: json }
7368
+ });
7369
+ }
7370
+ flushedTools.add(id);
7371
+ }
7372
+ } else if (openType !== "tool") {
7334
7373
  const sig = grabRoundTripSignature(part);
7335
7374
  openBlock("tool", {
7336
7375
  type: "tool_use",
7337
- id: encodeToolUseId(part.toolCallId ?? "", sig),
7376
+ id: encodeToolUseId(id, sig),
7338
7377
  name: part.toolName,
7339
7378
  input: {}
7340
7379
  });
7341
7380
  emit("content_block_delta", {
7342
7381
  type: "content_block_delta",
7343
7382
  index: blockIndex,
7344
- delta: { type: "input_json_delta", partial_json: JSON.stringify(stripNullInputs(part.input ?? {})) }
7383
+ delta: { type: "input_json_delta", partial_json: JSON.stringify(sanitizeToolInput(part.input ?? {}, requiredProps.get(part.toolName ?? ""))) }
7345
7384
  });
7385
+ flushedTools.add(id);
7346
7386
  }
7347
7387
  break;
7348
7388
  }
@@ -7407,7 +7447,7 @@ async function streamAnthropicResponse(model, params, modelId, write, log12, obs
7407
7447
  }
7408
7448
  })();
7409
7449
  try {
7410
- await writeAnthropicStream(watchedStream, modelId, write, log12, { ...observer, abortSignal });
7450
+ await writeAnthropicStream(watchedStream, modelId, write, log12, { ...observer, abortSignal }, params.tools);
7411
7451
  } finally {
7412
7452
  stopForwardingAbort();
7413
7453
  clearTimeout(idleTimer);
@@ -7498,6 +7538,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
7498
7538
  if (!generateAbort.signal.aborted) generateAbort.abort();
7499
7539
  }
7500
7540
  }
7541
+ const requiredProps = toolRequiredProps(params.tools);
7501
7542
  return {
7502
7543
  id: "msg_" + Date.now(),
7503
7544
  type: "message",
@@ -7509,7 +7550,7 @@ async function generateAnthropicResponse(model, params, modelId, options) {
7509
7550
  type: "tool_use",
7510
7551
  id: encodeToolUseId(tc.toolCallId, grabRoundTripSignature(tc)),
7511
7552
  name: tc.toolName,
7512
- input: stripNullInputs(tc.input)
7553
+ input: sanitizeToolInput(tc.input ?? {}, requiredProps.get(tc.toolName))
7513
7554
  }))
7514
7555
  ],
7515
7556
  stop_reason: finishReason === "tool-calls" ? "tool_use" : "end_turn",
@@ -7558,6 +7599,8 @@ function anthropicPromptTooLongMessage(body, contextWindow) {
7558
7599
  }
7559
7600
 
7560
7601
  // src/proxy.ts
7602
+ var STREAM_KEEPALIVE_INTERVAL_MS = 2e4;
7603
+ var STREAM_KEEPALIVE_PING = 'event: ping\ndata: {"type":"ping"}\n\n';
7561
7604
  function createTranslationLifecycle(logPath, requestId, modelId, provider) {
7562
7605
  if (!logPath || !requestId) return void 0;
7563
7606
  const startedAt = Date.now();
@@ -7901,6 +7944,9 @@ function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPa
7901
7944
  });
7902
7945
  translationLifecycle?.dispatched();
7903
7946
  if (clientWantsStream) {
7947
+ const keepAliveMs = Number(process.env.CLODEX_STREAM_KEEPALIVE_INTERVAL_MS) || STREAM_KEEPALIVE_INTERVAL_MS;
7948
+ let lastDownstreamWriteAt = Date.now();
7949
+ let lastUpstreamPartAt = Date.now();
7904
7950
  const writeStreamChunk = (chunk) => {
7905
7951
  translationLifecycle?.onOutput(chunk);
7906
7952
  if (!res.headersSent) {
@@ -7910,23 +7956,43 @@ function startProxyCatalog(routes, defaultAliasId, debug = false, inferenceLogPa
7910
7956
  "Connection": "keep-alive"
7911
7957
  });
7912
7958
  }
7959
+ lastDownstreamWriteAt = Date.now();
7913
7960
  res.write(chunk);
7914
7961
  };
7915
- await withResponsesWebSocketDiagnosticContext(
7916
- { requestId: relayRequestId, claudeSessionId },
7917
- () => streamAnthropicResponse(
7918
- model,
7919
- params,
7920
- originalModel,
7921
- writeStreamChunk,
7922
- plog,
7923
- {
7924
- onPart: (partType) => translationLifecycle?.onPart(partType),
7925
- initialInputTokens: estimateAnthropicInputTokens(anthropicBody),
7926
- abortSignal: clientAbort.signal
7927
- }
7928
- )
7929
- );
7962
+ const keepAlive = setInterval(() => {
7963
+ if (res.writableEnded || !res.headersSent) return;
7964
+ const now = Date.now();
7965
+ const outputIdleMs = now - lastDownstreamWriteAt;
7966
+ const upstreamIdleMs = now - lastUpstreamPartAt;
7967
+ if (outputIdleMs >= keepAliveMs && upstreamIdleMs < keepAliveMs) {
7968
+ lastDownstreamWriteAt = now;
7969
+ res.write(STREAM_KEEPALIVE_PING);
7970
+ plog(() => `stream keepalive ping: output idle ${outputIdleMs}ms, upstream active (${upstreamIdleMs}ms since last part)`);
7971
+ }
7972
+ }, keepAliveMs);
7973
+ keepAlive.unref();
7974
+ try {
7975
+ await withResponsesWebSocketDiagnosticContext(
7976
+ { requestId: relayRequestId, claudeSessionId },
7977
+ () => streamAnthropicResponse(
7978
+ model,
7979
+ params,
7980
+ originalModel,
7981
+ writeStreamChunk,
7982
+ plog,
7983
+ {
7984
+ onPart: (partType) => {
7985
+ lastUpstreamPartAt = Date.now();
7986
+ translationLifecycle?.onPart(partType);
7987
+ },
7988
+ initialInputTokens: estimateAnthropicInputTokens(anthropicBody),
7989
+ abortSignal: clientAbort.signal
7990
+ }
7991
+ )
7992
+ );
7993
+ } finally {
7994
+ clearInterval(keepAlive);
7995
+ }
7930
7996
  translationLifecycle?.complete();
7931
7997
  if (!res.headersSent) writeStreamChunk("");
7932
7998
  res.end();