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