@combycode/llm-sdk 1.0.0 → 1.1.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/index.d.ts CHANGED
@@ -46,10 +46,10 @@ export type { AudioFormat, AudioOptions, AudioInput } from './llm/types/audio';
46
46
  export { contentParts, contentText } from './llm/types/messages';
47
47
  export type { Role, MessageOrigin, ContentPart, TextPart, ImagePart, DocumentPart, AudioPart, VideoPart, ToolCallPart, ToolResultPart, ImageOutputPart, AudioOutputPart, VideoOutputPart, MediaOutputPart, DataSource, Content, Message } from './llm/types/messages';
48
48
  export { isFunctionTool, isBuiltinTool } from './llm/types/tools';
49
- export type { FunctionTool, BuiltinTool, Tool, ToolChoice, JsonSchema } from './llm/types/tools';
49
+ export type { FunctionTool, BuiltinTool, McpToolParams, Tool, ToolChoice, JsonSchema } from './llm/types/tools';
50
50
  export { emptyUsage } from './llm/types/response';
51
- export type { CompletionResponse, FinishReason, Usage } from './llm/types/response';
52
- export type { NormalizedRequest, ThinkingConfig, CacheConfig } from './llm/types/request';
51
+ export type { CompletionResponse, FileOutput, FinishReason, Usage } from './llm/types/response';
52
+ export type { NormalizedRequest, ReasoningContext, ThinkingConfig, CacheConfig } from './llm/types/request';
53
53
  export type { MediaStreamType, StreamEvent } from './llm/types/stream';
54
54
  export type { ExecuteOptions } from './llm/types/options';
55
55
  export type { ProviderName, ApiType, ProviderConfig, ProviderHttpRequest, ProviderAdapter } from './llm/types/provider';
@@ -247,6 +247,7 @@ export { embed } from './helpers/embed';
247
247
  export type { EmbedOptions } from './helpers/embed';
248
248
  export { moderate } from './helpers/moderate';
249
249
  export type { ModerateOptions, ModerationCategories, ModerationContentPart, ModerationImageUrlPart, ModerationResult, ModerationScores, ModerationTextPart } from './helpers/moderate-types';
250
+ export type { ModerationEntry, ModerationReport, ModerationRequest, ModerationStreamOptions, ModerationStreamStrategy } from './llm/moderation/types';
250
251
  export { listModels, listModelsLive, clearLiveModelsCache } from './helpers/models';
251
252
  export type { ListModelsLiveOptions } from './helpers/models';
252
253
  export { select, selectModels } from './helpers/select-model';
package/dist/index.js CHANGED
@@ -22104,6 +22104,7 @@ var ModelCatalog = class {
22104
22104
  family: info.family,
22105
22105
  version: info.version,
22106
22106
  status: info.status,
22107
+ availability: info.availability,
22107
22108
  active: info.active,
22108
22109
  deprecation: info.deprecation
22109
22110
  });
@@ -22191,6 +22192,168 @@ var ModelCatalog = class {
22191
22192
  }
22192
22193
  };
22193
22194
 
22195
+ // src/llm/providers/openai/moderations.ts
22196
+ var OPENAI_MODERATION_BASE_URL = "https://api.openai.com";
22197
+ var OPENAI_MODERATION_PATH = "/v1/moderations";
22198
+ var OPENAI_MODERATION_DEFAULT_MODEL = "omni-moderation-latest";
22199
+ var OpenAIModerationAdapter = class {
22200
+ apiKey;
22201
+ baseURL;
22202
+ constructor(config) {
22203
+ this.apiKey = config.apiKey;
22204
+ this.baseURL = config.baseURL ?? OPENAI_MODERATION_BASE_URL;
22205
+ }
22206
+ async moderate(input, model, fetch2) {
22207
+ const res = await fetch2({
22208
+ url: `${this.baseURL}${OPENAI_MODERATION_PATH}`,
22209
+ method: "POST",
22210
+ headers: { authorization: `Bearer ${this.apiKey}`, "content-type": "application/json" },
22211
+ body: { model, input },
22212
+ provider: "openai",
22213
+ model,
22214
+ responseType: "json"
22215
+ });
22216
+ if (res.status >= 400) {
22217
+ throw new Error(`OpenAI moderations failed (${res.status}): ${JSON.stringify(res.body)}`);
22218
+ }
22219
+ const data = res.body;
22220
+ return (data.results ?? []).map(parseRawResult);
22221
+ }
22222
+ };
22223
+ function parseRawResult(r) {
22224
+ return {
22225
+ flagged: r.flagged,
22226
+ categories: r.categories,
22227
+ categoryScores: r.category_scores,
22228
+ categoryAppliedInputTypes: r.category_applied_input_types
22229
+ };
22230
+ }
22231
+
22232
+ // src/llm/moderation/types.ts
22233
+ var MODERATION_DEFAULT_MODEL = "omni-moderation-latest";
22234
+ var MODERATION_DEFAULT_STRATEGY = "buffer";
22235
+ var MODERATION_DEFAULT_INTERVAL = 400;
22236
+
22237
+ // src/llm/moderation/runner.ts
22238
+ function resolveModerationMode(provider, mod) {
22239
+ if (mod.mode) return mod.mode;
22240
+ return provider === "openai" ? "native" : "emulate";
22241
+ }
22242
+ function moderationInputText(messages) {
22243
+ const lastUser = [...messages].reverse().find((m) => m.role === "user");
22244
+ if (!lastUser) return "";
22245
+ return typeof lastUser.content === "string" ? lastUser.content : contentText(lastUser.content);
22246
+ }
22247
+ function emptyResult() {
22248
+ return {
22249
+ flagged: false,
22250
+ categories: {},
22251
+ categoryScores: {}
22252
+ };
22253
+ }
22254
+ async function runModeration(text, cfg) {
22255
+ if (!text) return emptyResult();
22256
+ try {
22257
+ const adapter = new OpenAIModerationAdapter({ apiKey: cfg.apiKey });
22258
+ const results = await adapter.moderate(text, cfg.model, cfg.fetch);
22259
+ return results[0] ?? emptyResult();
22260
+ } catch (e) {
22261
+ return { error: e instanceof Error ? e.message : String(e) };
22262
+ }
22263
+ }
22264
+ function moderationModel(mod) {
22265
+ return mod.model ?? MODERATION_DEFAULT_MODEL;
22266
+ }
22267
+ function emitModerationZeroCost(hooks, model) {
22268
+ const entry = {
22269
+ id: crypto.randomUUID(),
22270
+ timestamp: Date.now(),
22271
+ provider: "openai",
22272
+ model,
22273
+ tokens: { input: 0, output: 0, cached: 0, cacheWrite: 0, reasoning: 0 },
22274
+ cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, reasoning: 0, total: 0, source: "calculated" },
22275
+ providerEvidence: { note: "free: moderations endpoint not billed" },
22276
+ tags: { provider: "openai", model, type: "moderation" }
22277
+ };
22278
+ hooks.emitSync("onCostEntry", { entry, runningTotal: 0 });
22279
+ }
22280
+ function outputEvent(result) {
22281
+ return { type: "moderation", phase: "output", result, source: "emulated" };
22282
+ }
22283
+ function wrapModeratedStream(raw, strategy, interval, moderate2) {
22284
+ const step = interval > 0 ? interval : MODERATION_DEFAULT_INTERVAL;
22285
+ if (strategy === "post") return wrapPost(raw, moderate2);
22286
+ if (strategy === "parallel") return wrapParallel(raw, step, moderate2);
22287
+ return wrapBuffer(raw, step, moderate2);
22288
+ }
22289
+ async function* wrapPost(raw, moderate2) {
22290
+ let acc = "";
22291
+ for await (const ev of raw) {
22292
+ if (ev.type === "text") acc += ev.text;
22293
+ yield ev;
22294
+ }
22295
+ if (acc) yield outputEvent(await moderate2(acc));
22296
+ }
22297
+ async function* wrapBuffer(raw, interval, moderate2) {
22298
+ let acc = "";
22299
+ let checkedAt = 0;
22300
+ let hold = [];
22301
+ for await (const ev of raw) {
22302
+ hold.push(ev);
22303
+ if (ev.type === "text") {
22304
+ acc += ev.text;
22305
+ if (acc.length - checkedAt >= interval || ev.text.includes("\n")) {
22306
+ checkedAt = acc.length;
22307
+ yield outputEvent(await moderate2(acc));
22308
+ for (const h of hold) yield h;
22309
+ hold = [];
22310
+ }
22311
+ }
22312
+ }
22313
+ if (acc.length > checkedAt) yield outputEvent(await moderate2(acc));
22314
+ for (const h of hold) yield h;
22315
+ }
22316
+ async function* wrapParallel(raw, interval, moderate2) {
22317
+ const iter = raw[Symbol.asyncIterator]();
22318
+ const pending = /* @__PURE__ */ new Set();
22319
+ let acc = "";
22320
+ let checkedAt = 0;
22321
+ let rawDone = false;
22322
+ let rawNext = iter.next();
22323
+ const schedule = (text) => {
22324
+ const p = moderate2(text).then((result) => {
22325
+ pending.delete(p);
22326
+ return { tag: "mod", event: outputEvent(result) };
22327
+ });
22328
+ pending.add(p);
22329
+ };
22330
+ while (!rawDone || pending.size > 0) {
22331
+ const racers = [];
22332
+ if (!rawDone) racers.push(rawNext.then((result) => ({ tag: "raw", result })));
22333
+ for (const p of pending) racers.push(p);
22334
+ const winner = await Promise.race(racers);
22335
+ if (winner.tag === "mod") {
22336
+ yield winner.event;
22337
+ continue;
22338
+ }
22339
+ if (winner.result.done) {
22340
+ rawDone = true;
22341
+ if (acc.length > checkedAt) schedule(acc);
22342
+ continue;
22343
+ }
22344
+ const ev = winner.result.value;
22345
+ yield ev;
22346
+ if (ev.type === "text") {
22347
+ acc += ev.text;
22348
+ if (acc.length - checkedAt >= interval) {
22349
+ checkedAt = acc.length;
22350
+ schedule(acc);
22351
+ }
22352
+ }
22353
+ rawNext = iter.next();
22354
+ }
22355
+ }
22356
+
22194
22357
  // src/util/duration.ts
22195
22358
  var UNIT_MS = {
22196
22359
  s: 1e3,
@@ -22327,6 +22490,7 @@ var LLMClient = class {
22327
22490
  mode;
22328
22491
  batchable;
22329
22492
  adapter;
22493
+ apiKey;
22330
22494
  fetchFn;
22331
22495
  fetchStreamFn;
22332
22496
  priority;
@@ -22350,6 +22514,7 @@ var LLMClient = class {
22350
22514
  this.provider = config.provider;
22351
22515
  this.model = config.model;
22352
22516
  this.system = config.system;
22517
+ this.apiKey = config.apiKey;
22353
22518
  this.hooks = config.hooks ?? new HookBus();
22354
22519
  this.fetchFn = config.fetch;
22355
22520
  this.fetchStreamFn = config.fetchStream ?? null;
@@ -22403,6 +22568,34 @@ var LLMClient = class {
22403
22568
  }
22404
22569
  };
22405
22570
  }
22571
+ // ─── Moderation helpers ───────────────────────────────────────────────
22572
+ /** Resolve the OpenAI key the emulated moderation path needs. Reuses the
22573
+ * client's own key when the provider is OpenAI; otherwise requires an explicit
22574
+ * one. Throws when none is resolvable (report-only feature, but the call still
22575
+ * needs a key to reach the moderations endpoint). */
22576
+ resolveModerationKey(mod) {
22577
+ const key = mod.apiKey ?? (this.provider === "openai" ? this.apiKey : void 0);
22578
+ if (!key) {
22579
+ throw new Error(
22580
+ "moderation: emulated moderation requires an OpenAI API key (the only public moderations endpoint). Pass moderation.apiKey, or use the OpenAI provider."
22581
+ );
22582
+ }
22583
+ return key;
22584
+ }
22585
+ moderationConfig(mod) {
22586
+ return {
22587
+ apiKey: this.resolveModerationKey(mod),
22588
+ model: moderationModel(mod),
22589
+ fetch: this.fetchFn
22590
+ };
22591
+ }
22592
+ /** Fold a moderation stream event into the accumulating report. */
22593
+ mergeModeration(prev, phase, result, source) {
22594
+ const next = prev && prev.source === source ? { ...prev } : { source };
22595
+ if (phase === "input") next.input = result;
22596
+ else next.output = result;
22597
+ return next;
22598
+ }
22406
22599
  /** Submit a request. Returns the parsed CompletionResponse. */
22407
22600
  async complete(input, options = {}) {
22408
22601
  const rawMessages = normalizeInput(input);
@@ -22423,6 +22616,7 @@ var LLMClient = class {
22423
22616
  thinking: options.thinking,
22424
22617
  cache: options.cache,
22425
22618
  serviceTier: options.serviceTier,
22619
+ moderation: options.moderation,
22426
22620
  providerOptions: options.providerOptions,
22427
22621
  audio: options.audio,
22428
22622
  outputModalities: options.outputModalities,
@@ -22478,6 +22672,8 @@ var LLMClient = class {
22478
22672
  await this.hooks.emit("onBeforeSubmit", submitCtx);
22479
22673
  const inputChars = JSON.stringify(normalized.messages).length;
22480
22674
  const estimatedInputTokens = Math.ceil(inputChars / 4);
22675
+ const mod = options.moderation;
22676
+ const moderationCfg = mod && resolveModerationMode(this.provider, mod) === "emulate" ? this.moderationConfig(mod) : void 0;
22481
22677
  const start = performance.now();
22482
22678
  let response;
22483
22679
  if (submitCtx.intercepted && submitCtx.resultPromise) {
@@ -22503,6 +22699,22 @@ var LLMClient = class {
22503
22699
  }
22504
22700
  const latencyMs = performance.now() - start;
22505
22701
  const result = this.adapter.parseResponse(response.body, latencyMs);
22702
+ if (mod && moderationCfg) {
22703
+ const cfg = moderationCfg;
22704
+ const doInput = mod.input ?? true;
22705
+ const doOutput = mod.output ?? true;
22706
+ const [inputEntry, outputEntry] = await Promise.all([
22707
+ doInput ? runModeration(moderationInputText(normalized.messages), cfg) : Promise.resolve(void 0),
22708
+ doOutput ? runModeration(result.text, cfg) : Promise.resolve(void 0)
22709
+ ]);
22710
+ if (doInput) emitModerationZeroCost(this.hooks, cfg.model);
22711
+ if (doOutput) emitModerationZeroCost(this.hooks, cfg.model);
22712
+ result.moderation = {
22713
+ source: "emulated",
22714
+ ...inputEntry ? { input: inputEntry } : {},
22715
+ ...outputEntry ? { output: outputEntry } : {}
22716
+ };
22717
+ }
22506
22718
  await this.hooks.emit("onCompletion", {
22507
22719
  provider: this.provider,
22508
22720
  model: this.model,
@@ -22550,6 +22762,7 @@ var LLMClient = class {
22550
22762
  thinking: options.thinking,
22551
22763
  cache: options.cache,
22552
22764
  serviceTier: options.serviceTier,
22765
+ moderation: options.moderation,
22553
22766
  providerOptions: options.providerOptions,
22554
22767
  audio: options.audio,
22555
22768
  outputModalities: options.outputModalities,
@@ -22593,29 +22806,65 @@ var LLMClient = class {
22593
22806
  let thinking = "";
22594
22807
  let usage = emptyUsage();
22595
22808
  let finishReason = "stop";
22596
- for await (const sseEvent of this.fetchStreamFn(httpReq, {
22597
- queueName: this.queueName,
22598
- priority: this.priority,
22599
- ctx
22600
- })) {
22601
- const events = this.adapter.parseStreamEvent(sseEvent);
22602
- for (const event of events) {
22603
- switch (event.type) {
22604
- case "text":
22605
- text += event.text;
22606
- break;
22607
- case "thinking":
22608
- thinking += event.text;
22609
- break;
22610
- case "usage":
22611
- usage = event.usage;
22612
- break;
22613
- case "done":
22614
- finishReason = event.finishReason;
22615
- break;
22616
- }
22617
- yield event;
22809
+ let moderationReport;
22810
+ const fetchStream = this.fetchStreamFn;
22811
+ const adapter = this.adapter;
22812
+ const queueName = this.queueName;
22813
+ const priority = this.priority;
22814
+ async function* rawEvents() {
22815
+ for await (const sseEvent of fetchStream(httpReq, {
22816
+ queueName,
22817
+ priority,
22818
+ ctx
22819
+ })) {
22820
+ for (const ev of adapter.parseStreamEvent(sseEvent)) yield ev;
22821
+ }
22822
+ }
22823
+ const mod = options.moderation;
22824
+ const emulate = !!mod && resolveModerationMode(this.provider, mod) === "emulate";
22825
+ let eventStream = rawEvents();
22826
+ if (mod && emulate && (mod.output ?? true)) {
22827
+ const cfg = this.moderationConfig(mod);
22828
+ const hooks = this.hooks;
22829
+ const strategy = mod.stream?.strategy ?? MODERATION_DEFAULT_STRATEGY;
22830
+ const interval = mod.stream?.interval ?? MODERATION_DEFAULT_INTERVAL;
22831
+ eventStream = wrapModeratedStream(eventStream, strategy, interval, async (t) => {
22832
+ const r = await runModeration(t, cfg);
22833
+ emitModerationZeroCost(hooks, cfg.model);
22834
+ return r;
22835
+ });
22836
+ }
22837
+ if (mod && emulate && (mod.input ?? true)) {
22838
+ const cfg = this.moderationConfig(mod);
22839
+ const inputEntry = await runModeration(moderationInputText(normalized.messages), cfg);
22840
+ emitModerationZeroCost(this.hooks, cfg.model);
22841
+ moderationReport = this.mergeModeration(moderationReport, "input", inputEntry, "emulated");
22842
+ yield { type: "moderation", phase: "input", result: inputEntry, source: "emulated" };
22843
+ }
22844
+ for await (const event of eventStream) {
22845
+ switch (event.type) {
22846
+ case "text":
22847
+ text += event.text;
22848
+ break;
22849
+ case "thinking":
22850
+ thinking += event.text;
22851
+ break;
22852
+ case "usage":
22853
+ usage = event.usage;
22854
+ break;
22855
+ case "done":
22856
+ finishReason = event.finishReason;
22857
+ break;
22858
+ case "moderation":
22859
+ moderationReport = this.mergeModeration(
22860
+ moderationReport,
22861
+ event.phase,
22862
+ event.result,
22863
+ event.source
22864
+ );
22865
+ break;
22618
22866
  }
22867
+ yield event;
22619
22868
  }
22620
22869
  const inputChars = JSON.stringify(normalized.messages).length;
22621
22870
  const response = {
@@ -22628,6 +22877,7 @@ var LLMClient = class {
22628
22877
  toolCalls: [],
22629
22878
  thinking: thinking || null,
22630
22879
  media: [],
22880
+ ...moderationReport ? { moderation: moderationReport } : {},
22631
22881
  latencyMs: performance.now() - start,
22632
22882
  raw: null
22633
22883
  };
@@ -22936,6 +23186,9 @@ var AnthropicAdapter = class {
22936
23186
  if (t.type === "web_search") {
22937
23187
  return { type: "web_search_20250305", name: "web_search", max_uses: 5 };
22938
23188
  }
23189
+ if (t.type === "code_interpreter") {
23190
+ return { type: "code_execution_20260521", name: "code_execution" };
23191
+ }
22939
23192
  return null;
22940
23193
  }
22941
23194
  const tool = {
@@ -23046,6 +23299,7 @@ var AnthropicAdapter = class {
23046
23299
  const content = [];
23047
23300
  let thinking = null;
23048
23301
  const toolCalls = [];
23302
+ const files = [];
23049
23303
  for (const block of contentBlocks) {
23050
23304
  if (block.type === "text") {
23051
23305
  content.push({ type: "text", text: block.text });
@@ -23060,6 +23314,15 @@ var AnthropicAdapter = class {
23060
23314
  };
23061
23315
  content.push(tc);
23062
23316
  toolCalls.push(tc);
23317
+ } else if (block.type === "code_execution_tool_result") {
23318
+ const result = block.content;
23319
+ if (result?.type === "code_execution_result" && Array.isArray(result.content)) {
23320
+ for (const out of result.content) {
23321
+ if (out.type === "code_execution_output" && typeof out.file_id === "string") {
23322
+ files.push({ id: out.file_id, source: "code_execution" });
23323
+ }
23324
+ }
23325
+ }
23063
23326
  }
23064
23327
  }
23065
23328
  const finishReason = extractFinishReason(toolCalls.length > 0, r.stop_reason, {
@@ -23074,6 +23337,7 @@ var AnthropicAdapter = class {
23074
23337
  text: content.filter((p) => p.type === "text").map((p) => p.text).join(""),
23075
23338
  toolCalls,
23076
23339
  media: [],
23340
+ ...files.length ? { files } : {},
23077
23341
  thinking,
23078
23342
  latencyMs,
23079
23343
  raw
@@ -23379,6 +23643,17 @@ function resolveVoice(provider, voice) {
23379
23643
  return VOICE_ALIASES[provider]?.[voice] ?? voice;
23380
23644
  }
23381
23645
 
23646
+ // src/llm/providers/google/tiers.ts
23647
+ var REQUEST = /* @__PURE__ */ new Set(["flex", "standard", "priority"]);
23648
+ function googleRequestTier(t) {
23649
+ if (!t) return void 0;
23650
+ return REQUEST.has(t) ? t : void 0;
23651
+ }
23652
+ function googleBilledTier(raw) {
23653
+ if (typeof raw !== "string" || !raw) return {};
23654
+ return { serviceTier: raw, pricingTier: raw.toLowerCase() };
23655
+ }
23656
+
23382
23657
  // src/llm/providers/google/constants.ts
23383
23658
  var GOOGLE_THINKING_LEVELS = {
23384
23659
  low: "LOW",
@@ -23431,6 +23706,8 @@ var GoogleAdapter = class {
23431
23706
  contents,
23432
23707
  generationConfig: config
23433
23708
  };
23709
+ const tier = googleRequestTier(req.serviceTier);
23710
+ if (tier) body.serviceTier = tier;
23434
23711
  if (req.system) {
23435
23712
  body.systemInstruction = { parts: [{ text: req.system }] };
23436
23713
  }
@@ -23689,7 +23966,9 @@ var GoogleAdapter = class {
23689
23966
  totalTokens: u.totalTokenCount ?? input + output,
23690
23967
  cachedTokens: u.cachedContentTokenCount ?? 0,
23691
23968
  cacheWriteTokens: 0,
23692
- reasoningTokens: u.thoughtsTokenCount ?? 0
23969
+ reasoningTokens: u.thoughtsTokenCount ?? 0,
23970
+ // Billed service tier (output-only `usageMetadata.serviceTier`).
23971
+ ...googleBilledTier(u.serviceTier)
23693
23972
  };
23694
23973
  }
23695
23974
  };
@@ -24564,8 +24843,48 @@ var OpenAIBatchAdapter = class {
24564
24843
  }
24565
24844
  };
24566
24845
 
24846
+ // src/llm/moderation/native.ts
24847
+ function buildNativeModeration(mod) {
24848
+ return { model: mod.model ?? MODERATION_DEFAULT_MODEL };
24849
+ }
24850
+ function parseNativeModeration(raw) {
24851
+ if (!raw || typeof raw !== "object") return void 0;
24852
+ const m = raw;
24853
+ const input = parseEntry(m.input);
24854
+ const output = parseEntry(m.output);
24855
+ if (!input && !output) return void 0;
24856
+ return {
24857
+ source: "native",
24858
+ ...input ? { input } : {},
24859
+ ...output ? { output } : {}
24860
+ };
24861
+ }
24862
+ function parseEntry(entry) {
24863
+ if (!entry || typeof entry !== "object") return void 0;
24864
+ const e = entry;
24865
+ if (e.type === "error") {
24866
+ return { error: String(e.message ?? e.code ?? "moderation error") };
24867
+ }
24868
+ if (e.type === "moderation_results" && Array.isArray(e.results)) {
24869
+ const first = e.results[0];
24870
+ return first ? toResult(first) : void 0;
24871
+ }
24872
+ if (e.type === "moderation_result" || e.categories) {
24873
+ return toResult(e);
24874
+ }
24875
+ return void 0;
24876
+ }
24877
+ function toResult(r) {
24878
+ return {
24879
+ flagged: Boolean(r.flagged),
24880
+ categories: r.categories ?? {},
24881
+ categoryScores: r.category_scores ?? {},
24882
+ categoryAppliedInputTypes: r.category_applied_input_types
24883
+ };
24884
+ }
24885
+
24567
24886
  // src/llm/providers/openai/tiers.ts
24568
- var REQUEST = {
24887
+ var REQUEST2 = {
24569
24888
  auto: "auto",
24570
24889
  standard: "default",
24571
24890
  priority: "priority",
@@ -24575,7 +24894,7 @@ var REQUEST = {
24575
24894
  var KNOWN = /* @__PURE__ */ new Set(["auto", "default", "flex", "scale", "priority"]);
24576
24895
  function openaiRequestTier(t) {
24577
24896
  if (!t) return void 0;
24578
- const mapped = REQUEST[t] ?? t;
24897
+ const mapped = REQUEST2[t] ?? t;
24579
24898
  return KNOWN.has(mapped) ? mapped : "auto";
24580
24899
  }
24581
24900
  function openaiBilledTier(raw) {
@@ -24634,6 +24953,9 @@ var OpenAIAdapter = class {
24634
24953
  if (req.stop) body.stop = req.stop;
24635
24954
  const tier = openaiRequestTier(req.serviceTier);
24636
24955
  if (tier) body.service_tier = tier;
24956
+ if (req.moderation && req.moderation.mode !== "emulate") {
24957
+ body.moderation = buildNativeModeration(req.moderation);
24958
+ }
24637
24959
  const hasAudioInput = req.messages.some(
24638
24960
  (m) => Array.isArray(m.content) && m.content.some((p) => p.type === "audio")
24639
24961
  );
@@ -24792,6 +25114,7 @@ var OpenAIAdapter = class {
24792
25114
  { tool_calls: "tool_use", length: "length", content_filter: "content_filter" }
24793
25115
  );
24794
25116
  const reasoningContent = message.reasoning_content ?? null;
25117
+ const moderation = parseNativeModeration(r.moderation);
24795
25118
  return {
24796
25119
  id: r.id,
24797
25120
  model: r.model,
@@ -24802,12 +25125,22 @@ var OpenAIAdapter = class {
24802
25125
  toolCalls,
24803
25126
  media,
24804
25127
  thinking: reasoningContent,
25128
+ ...moderation ? { moderation } : {},
24805
25129
  latencyMs,
24806
25130
  raw
24807
25131
  };
24808
25132
  }
24809
25133
  parseStreamEvent(event) {
24810
25134
  const data = JSON.parse(event.data);
25135
+ if (data.moderation) {
25136
+ const report = parseNativeModeration(data.moderation);
25137
+ const out = [];
25138
+ if (report?.input)
25139
+ out.push({ type: "moderation", phase: "input", result: report.input, source: "native" });
25140
+ if (report?.output)
25141
+ out.push({ type: "moderation", phase: "output", result: report.output, source: "native" });
25142
+ if (out.length) return out;
25143
+ }
24811
25144
  const choices = data.choices ?? [];
24812
25145
  const choice = choices[0];
24813
25146
  if (!choice) {
@@ -25408,6 +25741,9 @@ var OpenAIResponsesAdapter = class {
25408
25741
  if (req.topP !== void 0) body.top_p = req.topP;
25409
25742
  const tier = openaiRequestTier(req.serviceTier);
25410
25743
  if (tier) body.service_tier = tier;
25744
+ if (req.moderation && req.moderation.mode !== "emulate") {
25745
+ body.moderation = buildNativeModeration(req.moderation);
25746
+ }
25411
25747
  if (req.tools?.length) {
25412
25748
  body.tools = req.tools.map((t) => {
25413
25749
  if (isFunctionTool(t)) {
@@ -25446,7 +25782,9 @@ var OpenAIResponsesAdapter = class {
25446
25782
  if (req.thinking && req.thinking.mode !== "off") {
25447
25783
  body.reasoning = {
25448
25784
  effort: req.thinking.effort ?? "medium",
25449
- summary: "auto"
25785
+ summary: "auto",
25786
+ // Cross-turn reasoning persistence (gpt-5/o-series, Responses only).
25787
+ ...req.thinking.context ? { context: req.thinking.context } : {}
25450
25788
  };
25451
25789
  }
25452
25790
  return { body };
@@ -25590,6 +25928,7 @@ var OpenAIResponsesAdapter = class {
25590
25928
  text = r.output_text;
25591
25929
  if (text && content.length === 0) content.push({ type: "text", text });
25592
25930
  }
25931
+ const moderation = parseNativeModeration(r.moderation);
25593
25932
  return {
25594
25933
  id: r.id,
25595
25934
  model: r.model ?? "",
@@ -25600,6 +25939,7 @@ var OpenAIResponsesAdapter = class {
25600
25939
  toolCalls,
25601
25940
  thinking,
25602
25941
  media,
25942
+ ...moderation ? { moderation } : {},
25603
25943
  latencyMs,
25604
25944
  raw
25605
25945
  };
@@ -25654,6 +25994,11 @@ var OpenAIResponsesAdapter = class {
25654
25994
  }
25655
25995
  if (type === "response.completed") {
25656
25996
  const response = data.response ?? data;
25997
+ const moderation = parseNativeModeration(response.moderation);
25998
+ if (moderation?.input)
25999
+ events.push({ type: "moderation", phase: "input", result: moderation.input, source: "native" });
26000
+ if (moderation?.output)
26001
+ events.push({ type: "moderation", phase: "output", result: moderation.output, source: "native" });
25657
26002
  const usage = response.usage;
25658
26003
  if (usage) events.push({ type: "usage", usage: this.parseUsage(usage) });
25659
26004
  events.push({
@@ -33533,43 +33878,6 @@ function handoff(name, description, agent, opts = {}) {
33533
33878
  });
33534
33879
  }
33535
33880
 
33536
- // src/llm/providers/openai/moderations.ts
33537
- var OPENAI_MODERATION_BASE_URL = "https://api.openai.com";
33538
- var OPENAI_MODERATION_PATH = "/v1/moderations";
33539
- var OPENAI_MODERATION_DEFAULT_MODEL = "omni-moderation-latest";
33540
- var OpenAIModerationAdapter = class {
33541
- apiKey;
33542
- baseURL;
33543
- constructor(config) {
33544
- this.apiKey = config.apiKey;
33545
- this.baseURL = config.baseURL ?? OPENAI_MODERATION_BASE_URL;
33546
- }
33547
- async moderate(input, model, fetch2) {
33548
- const res = await fetch2({
33549
- url: `${this.baseURL}${OPENAI_MODERATION_PATH}`,
33550
- method: "POST",
33551
- headers: { authorization: `Bearer ${this.apiKey}`, "content-type": "application/json" },
33552
- body: { model, input },
33553
- provider: "openai",
33554
- model,
33555
- responseType: "json"
33556
- });
33557
- if (res.status >= 400) {
33558
- throw new Error(`OpenAI moderations failed (${res.status}): ${JSON.stringify(res.body)}`);
33559
- }
33560
- const data = res.body;
33561
- return (data.results ?? []).map(parseRawResult);
33562
- }
33563
- };
33564
- function parseRawResult(r) {
33565
- return {
33566
- flagged: r.flagged,
33567
- categories: r.categories,
33568
- categoryScores: r.category_scores,
33569
- categoryAppliedInputTypes: r.category_applied_input_types
33570
- };
33571
- }
33572
-
33573
33881
  // src/helpers/moderate.ts
33574
33882
  var MODERATION_COST_NOTE = "free: moderations endpoint not billed";
33575
33883
  var MODERATION_DEFAULT_PROVIDER = "openai";
@@ -36,6 +36,7 @@ export declare class LLMClient {
36
36
  readonly mode: 'foreground' | 'background';
37
37
  readonly batchable: boolean;
38
38
  private readonly adapter;
39
+ private readonly apiKey;
39
40
  private readonly fetchFn;
40
41
  private readonly fetchStreamFn;
41
42
  private readonly priority;
@@ -58,6 +59,14 @@ export declare class LLMClient {
58
59
  * const r2 = await llm.complete(messages); // sends id + only the new turn
59
60
  */
60
61
  assistantMessage(response: CompletionResponse): Message;
62
+ /** Resolve the OpenAI key the emulated moderation path needs. Reuses the
63
+ * client's own key when the provider is OpenAI; otherwise requires an explicit
64
+ * one. Throws when none is resolvable (report-only feature, but the call still
65
+ * needs a key to reach the moderations endpoint). */
66
+ private resolveModerationKey;
67
+ private moderationConfig;
68
+ /** Fold a moderation stream event into the accumulating report. */
69
+ private mergeModeration;
61
70
  /** Submit a request. Returns the parsed CompletionResponse. */
62
71
  complete(input: string | ContentPart[] | Message[], options?: ExecuteOptions): Promise<CompletionResponse>;
63
72
  /** Run `complete` with a JSON Schema enforced via `structured`. Strips any