@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/eval.cjs CHANGED
@@ -11206,6 +11206,77 @@ function parseHermesParams(inner) {
11206
11206
  }
11207
11207
  return sanitizeToolInput(input, { trim: true }).value;
11208
11208
  }
11209
+ var STREAM_MARKER = "<function=";
11210
+ var DEFAULT_STREAM_BUFFER_CAP = 8192;
11211
+ var isStreamWs = (c) => c === " " || c === " " || c === "\n" || c === "\r";
11212
+ function streamToolCallBufferState(held, allowedToolNames, cap = DEFAULT_STREAM_BUFFER_CAP) {
11213
+ if (allowedToolNames.size === 0) return "impossible";
11214
+ const t = held.trimStart();
11215
+ if (t.length < STREAM_MARKER.length) {
11216
+ return STREAM_MARKER.startsWith(t) ? "possible" : "impossible";
11217
+ }
11218
+ if (!t.startsWith(STREAM_MARKER)) return "impossible";
11219
+ const parsed = parseStreamMarkerName(t);
11220
+ if (parsed === "building") return "possible";
11221
+ if (parsed === "invalid") return "impossible";
11222
+ const nameOk = parsed.complete ? allowedToolNames.has(parsed.name) : someToolNameStartsWith(allowedToolNames, parsed.name);
11223
+ if (!nameOk) return "impossible";
11224
+ return held.length > cap ? "impossible" : "possible";
11225
+ }
11226
+ function parseStreamMarkerName(t) {
11227
+ let cursor = STREAM_MARKER.length;
11228
+ while (cursor < t.length && isStreamWs(t[cursor])) cursor += 1;
11229
+ const nameStart = cursor;
11230
+ while (cursor < t.length && t[cursor] !== ">" && !isStreamWs(t[cursor])) cursor += 1;
11231
+ const name = t.slice(nameStart, cursor);
11232
+ if (name.length === 0) return cursor >= t.length ? "building" : "invalid";
11233
+ return { name, complete: cursor < t.length && t[cursor] === ">" };
11234
+ }
11235
+ function someToolNameStartsWith(allowedToolNames, prefix) {
11236
+ for (const name of allowedToolNames) {
11237
+ if (name.startsWith(prefix)) return true;
11238
+ }
11239
+ return false;
11240
+ }
11241
+ function firstPossibleMarkerStart(held, allowedToolNames) {
11242
+ for (let i = held.indexOf("<"); i !== -1; i = held.indexOf("<", i + 1)) {
11243
+ if (streamToolCallBufferState(held.slice(i), allowedToolNames) === "possible") return i;
11244
+ }
11245
+ return -1;
11246
+ }
11247
+ var StreamSuppressionBuffer = class {
11248
+ constructor(allowedToolNames) {
11249
+ this.allowedToolNames = allowedToolNames;
11250
+ }
11251
+ allowedToolNames;
11252
+ #held = "";
11253
+ /** Feed a content delta; returns the text to emit as a `text_delta` now, or `undefined` to hold. */
11254
+ push(content) {
11255
+ this.#held += content;
11256
+ if (streamToolCallBufferState(this.#held, this.allowedToolNames) === "possible")
11257
+ return void 0;
11258
+ const holdStart = firstPossibleMarkerStart(this.#held, this.allowedToolNames);
11259
+ if (holdStart > 0) {
11260
+ const flush2 = this.#held.slice(0, holdStart);
11261
+ this.#held = this.#held.slice(holdStart);
11262
+ return flush2;
11263
+ }
11264
+ const flush = this.#held;
11265
+ this.#held = "";
11266
+ return flush;
11267
+ }
11268
+ /** Drain the held buffer at stream end. `hasNativeCalls` mirrors `finish()`'s size-guard: when
11269
+ * native `tool_calls` exist, `finish()` won't strip the leaked block, so stream the held text WHOLE
11270
+ * (keeping `accumulatedText == finish.text`); otherwise strip the recoverable blocks. Idempotent. */
11271
+ drain(hasNativeCalls) {
11272
+ if (this.#held.length === 0) return void 0;
11273
+ const held = this.#held;
11274
+ this.#held = "";
11275
+ if (hasNativeCalls) return held;
11276
+ const residual = extractHermesToolCalls(held, () => "held", this.allowedToolNames).residualText;
11277
+ return residual.length > 0 ? residual : void 0;
11278
+ }
11279
+ };
11209
11280
 
11210
11281
  // src/internal/llm/openai.ts
11211
11282
  var OpenAIClient = class {
@@ -11304,6 +11375,8 @@ var OpenAIClient = class {
11304
11375
  const events = accumulator.consume(chunk);
11305
11376
  for (const event of events) yield event;
11306
11377
  }
11378
+ const drainEvent = accumulator.finalizeHeldText();
11379
+ if (drainEvent !== void 0) yield drainEvent;
11307
11380
  return accumulator.finish();
11308
11381
  }
11309
11382
  };
@@ -11319,6 +11392,7 @@ var OpenAIStreamAccumulator = class {
11319
11392
  this.extractFromContent = extractFromContent;
11320
11393
  this.providerName = providerName;
11321
11394
  this.allowedToolNames = allowedToolNames;
11395
+ this.suppress = extractFromContent && allowedToolNames !== void 0 && allowedToolNames.size > 0 ? new StreamSuppressionBuffer(allowedToolNames) : void 0;
11322
11396
  }
11323
11397
  extractFromContent;
11324
11398
  providerName;
@@ -11331,18 +11405,30 @@ var OpenAIStreamAccumulator = class {
11331
11405
  cacheWriteTokens;
11332
11406
  reasoningTokens;
11333
11407
  toolCalls = /* @__PURE__ */ new Map();
11408
+ /** R7: present only when recovery is enabled AND the request declares tools — holds suspected
11409
+ * leaked-dialect content back from the `text_delta` stream. `undefined` ⇒ stream immediately. */
11410
+ suppress;
11334
11411
  consume(chunk) {
11335
11412
  const events = [];
11336
11413
  this.applyUsage(chunk.usage);
11337
11414
  for (const choice of chunk.choices ?? []) {
11338
- const reasoningEvent = this.applyReasoningDelta(
11339
- choice.delta?.reasoning ?? choice.delta?.reasoning_content
11340
- );
11341
- if (reasoningEvent !== void 0) events.push(reasoningEvent);
11342
- const textEvent = this.applyContentDelta(choice.delta?.content);
11343
- if (textEvent !== void 0) events.push(textEvent);
11344
- this.mergeToolCallDeltas(choice.delta?.tool_calls);
11345
- this.applyFinishReason(choice.finish_reason);
11415
+ events.push(...this.applyChoice(choice));
11416
+ }
11417
+ return events;
11418
+ }
11419
+ applyChoice(choice) {
11420
+ const events = [];
11421
+ const reasoningEvent = this.applyReasoningDelta(
11422
+ choice.delta?.reasoning ?? choice.delta?.reasoning_content
11423
+ );
11424
+ if (reasoningEvent !== void 0) events.push(reasoningEvent);
11425
+ const textEvent = this.applyContentDelta(choice.delta?.content);
11426
+ if (textEvent !== void 0) events.push(textEvent);
11427
+ this.mergeToolCallDeltas(choice.delta?.tool_calls);
11428
+ this.applyFinishReason(choice.finish_reason);
11429
+ if (choice.finish_reason !== void 0 && choice.finish_reason !== null) {
11430
+ const flushEvent = this.finalizeHeldText();
11431
+ if (flushEvent !== void 0) events.push(flushEvent);
11346
11432
  }
11347
11433
  return events;
11348
11434
  }
@@ -11367,7 +11453,17 @@ var OpenAIStreamAccumulator = class {
11367
11453
  applyContentDelta(content) {
11368
11454
  if (typeof content !== "string" || content.length === 0) return void 0;
11369
11455
  this.text += content;
11370
- return { type: "text_delta", text: content };
11456
+ if (this.suppress === void 0) return { type: "text_delta", text: content };
11457
+ const emit = this.suppress.push(content);
11458
+ return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
11459
+ }
11460
+ /** R7 held-buffer finalizer, called at the `finish_reason` chunk (in `applyChoice`) AND after the
11461
+ * SSE loop in `stream()` — so a stream that omits a `finish_reason` terminal never silently drops
11462
+ * held text. `toolCalls.size > 0` (native calls present) makes `finish()` skip recovery, so the
11463
+ * buffer streams the held text whole. Idempotent once drained. */
11464
+ finalizeHeldText() {
11465
+ const emit = this.suppress?.drain(this.toolCalls.size > 0);
11466
+ return emit !== void 0 ? { type: "text_delta", text: emit } : void 0;
11371
11467
  }
11372
11468
  mergeToolCallDeltas(deltas) {
11373
11469
  for (const call of deltas ?? []) {