@theokit/sdk 2.15.1 → 2.15.2

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/index.cjs CHANGED
@@ -13683,6 +13683,77 @@ function parseHermesParams(inner) {
13683
13683
  }
13684
13684
  return sanitizeToolInput(input, { trim: true }).value;
13685
13685
  }
13686
+ var STREAM_MARKER = "<function=";
13687
+ var DEFAULT_STREAM_BUFFER_CAP = 8192;
13688
+ var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
13689
+ function streamToolCallBufferState(held, allowedToolNames, cap2 = DEFAULT_STREAM_BUFFER_CAP) {
13690
+ if (allowedToolNames.size === 0) return "impossible";
13691
+ const t = held.trimStart();
13692
+ if (t.length < STREAM_MARKER.length) {
13693
+ return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
13694
+ }
13695
+ if (!t.startsWith(STREAM_MARKER)) return "impossible";
13696
+ const parsed = parseStreamMarkerName(t);
13697
+ if (parsed === "building") return "possible";
13698
+ if (parsed === "invalid") return "impossible";
13699
+ const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
13700
+ if (!nameOk) return "impossible";
13701
+ return held.length > cap2 ? "impossible" : "possible";
13702
+ }
13703
+ function parseStreamMarkerName(t) {
13704
+ let cursor = STREAM_MARKER.length;
13705
+ while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
13706
+ const nameStart = cursor;
13707
+ while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
13708
+ const name = t.slice(nameStart, cursor);
13709
+ if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
13710
+ return { name, complete: cursor < t.length && t[cursor] === ">" };
13711
+ }
13712
+ function someToolNameStartsWith(allowedToolNames, prefix) {
13713
+ for (const name of allowedToolNames) {
13714
+ if (name.startsWith(prefix)) return true;
13715
+ }
13716
+ return false;
13717
+ }
13718
+ function firstPossibleMarkerStart(held, allowedToolNames) {
13719
+ for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
13720
+ if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
13721
+ }
13722
+ return -1;
13723
+ }
13724
+ var StreamSuppressionBuffer = class {
13725
+ constructor(allowedToolNames) {
13726
+ this.allowedToolNames = allowedToolNames;
13727
+ }
13728
+ allowedToolNames;
13729
+ #held = "";
13730
+ /** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
13731
+ push(content) {
13732
+ this.#held += content;
13733
+ if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
13734
+ return void 0;
13735
+ const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
13736
+ if (holdStart > 0) {
13737
+ const flush2 = this.#held.slice(0, holdStart);
13738
+ this.#held = this.#held.slice(holdStart);
13739
+ return flush2;
13740
+ }
13741
+ const flush = this.#held;
13742
+ this.#held = "";
13743
+ return flush;
13744
+ }
13745
+ /** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
13746
+ * native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
13747
+ * (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
13748
+ drain(hasNativeCalls) {
13749
+ if (this.#held.length === 0) return void 0;
13750
+ const held = this.#held;
13751
+ this.#held = "";
13752
+ if (hasNativeCalls) return held;
13753
+ const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
13754
+ return residual.length > 0 ? residual : void 0;
13755
+ }
13756
+ };
13686
13757
 
13687
13758
  // src/internal/llm/openai.ts
13688
13759
  var OpenAIClient = class {
@@ -13781,6 +13852,8 @@ var OpenAIClient = class {
13781
13852
  const events = accumulator.consume(chunk);
13782
13853
  for (const event of events) yield event;
13783
13854
  }
13855
+ const drainEvent = accumulator.finalizeHeldText();
13856
+ if (drainEvent !== void 0) yield drainEvent;
13784
13857
  return accumulator.finish();
13785
13858
  }
13786
13859
  };
@@ -13796,6 +13869,7 @@ var OpenAIStreamAccumulator = class {
13796
13869
  this.extractFromContent = extractFromContent;
13797
13870
  this.providerName = providerName;
13798
13871
  this.allowedToolNames = allowedToolNames;
13872
+ this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
13799
13873
  }
13800
13874
  extractFromContent;
13801
13875
  providerName;
@@ -13808,18 +13882,30 @@ var OpenAIStreamAccumulator = class {
13808
13882
  cacheWriteTokens;
13809
13883
  reasoningTokens;
13810
13884
  toolCalls = /* @__PURE__ */ new Map();
13885
+ /** R7: present only when recovery is enabled AND the request declares tools — holds suspected
13886
+ * leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
13887
+ suppress;
13811
13888
  consume(chunk) {
13812
13889
  const events = [];
13813
13890
  this.applyUsage(chunk.usage);
13814
13891
  for (const choice of chunk.choices ?? []) {
13815
- const reasoningEvent = this.applyReasoningDelta(
13816
- choice.delta?.reasoning ?? choice.delta?.reasoning_content
13817
- );
13818
- if (reasoningEvent !== void 0) events.push(reasoningEvent);
13819
- const textEvent = this.applyContentDelta(choice.delta?.content);
13820
- if (textEvent !== void 0) events.push(textEvent);
13821
- this.mergeToolCallDeltas(choice.delta?.tool_calls);
13822
- this.applyFinishReason(choice.finish_reason);
13892
+ events.push(...this.applyChoice(choice));
13893
+ }
13894
+ return events;
13895
+ }
13896
+ applyChoice(choice) {
13897
+ const events = [];
13898
+ const reasoningEvent = this.applyReasoningDelta(
13899
+ choice.delta?.reasoning ?? choice.delta?.reasoning_content
13900
+ );
13901
+ if (reasoningEvent !== void 0) events.push(reasoningEvent);
13902
+ const textEvent = this.applyContentDelta(choice.delta?.content);
13903
+ if (textEvent !== void 0) events.push(textEvent);
13904
+ this.mergeToolCallDeltas(choice.delta?.tool_calls);
13905
+ this.applyFinishReason(choice.finish_reason);
13906
+ if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
13907
+ const flushEvent = this.finalizeHeldText();
13908
+ if (flushEvent !== void 0) events.push(flushEvent);
13823
13909
  }
13824
13910
  return events;
13825
13911
  }
@@ -13844,7 +13930,17 @@ var OpenAIStreamAccumulator = class {
13844
13930
  applyContentDelta(content) {
13845
13931
  if (typeof content !== "string" || content.length === 0) return void 0;
13846
13932
  this.text += content;
13847
- return { type: "text_delta", text: content };
13933
+ if (this.suppress === void 0) return { type: "text_delta", text: content };
13934
+ const emit2 = this.suppress.push(content);
13935
+ return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
13936
+ }
13937
+ /** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
13938
+ * SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
13939
+ * held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
13940
+ * buffer streams the held text whole. Idempotent once drained. */
13941
+ finalizeHeldText() {
13942
+ const emit2 = this.suppress?.drain(this.toolCalls.size > 0);
13943
+ return emit2 !== void 0 ? { type: "text_delta", text: emit2 } : void 0;
13848
13944
  }
13849
13945
  mergeToolCallDeltas(deltas) {
13850
13946
  for (const call of deltas ?? []) {