@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/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/dist/index.browser.js +371 -63
- package/dist/index.d.ts +4 -3
- package/dist/index.js +371 -63
- package/dist/llm/client.d.ts +9 -0
- package/dist/llm/moderation/native.d.ts +18 -0
- package/dist/llm/moderation/runner.d.ts +33 -0
- package/dist/llm/moderation/types.d.ts +75 -0
- package/dist/llm/providers/google/tiers.d.ts +15 -0
- package/dist/llm/types/options.d.ts +5 -0
- package/dist/llm/types/request.d.ts +10 -0
- package/dist/llm/types/response.d.ts +23 -0
- package/dist/llm/types/stream.d.ts +10 -0
- package/dist/llm/types/tools.d.ts +21 -0
- package/dist/plugins/model-catalog/catalog.d.ts +3 -0
- package/package.json +1 -1
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
|
-
|
|
22597
|
-
|
|
22598
|
-
|
|
22599
|
-
|
|
22600
|
-
|
|
22601
|
-
|
|
22602
|
-
for (const
|
|
22603
|
-
|
|
22604
|
-
|
|
22605
|
-
|
|
22606
|
-
|
|
22607
|
-
|
|
22608
|
-
|
|
22609
|
-
|
|
22610
|
-
|
|
22611
|
-
|
|
22612
|
-
|
|
22613
|
-
|
|
22614
|
-
|
|
22615
|
-
|
|
22616
|
-
|
|
22617
|
-
|
|
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
|
|
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 =
|
|
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";
|
package/dist/llm/client.d.ts
CHANGED
|
@@ -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
|