@combycode/llm-sdk 1.6.0 → 1.7.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.js CHANGED
@@ -4233,7 +4233,6 @@ var catalog_default2 = {
4233
4233
  },
4234
4234
  imageSize: {
4235
4235
  values: [
4236
- "512",
4237
4236
  "1K",
4238
4237
  "2K",
4239
4238
  "4K"
@@ -4398,7 +4397,6 @@ var catalog_default2 = {
4398
4397
  },
4399
4398
  imageSize: {
4400
4399
  values: [
4401
- "512",
4402
4400
  "1K",
4403
4401
  "2K",
4404
4402
  "4K"
@@ -5953,10 +5951,7 @@ var catalog_default2 = {
5953
5951
  },
5954
5952
  imageSize: {
5955
5953
  values: [
5956
- "512",
5957
- "1K",
5958
- "2K",
5959
- "4K"
5954
+ "1K"
5960
5955
  ],
5961
5956
  default: "1K"
5962
5957
  }
@@ -23423,7 +23418,8 @@ var catalog_default5 = {
23423
23418
  video: false,
23424
23419
  imageGeneration: false,
23425
23420
  audioGeneration: false,
23426
- videoGeneration: true
23421
+ videoGeneration: true,
23422
+ videoExtension: true
23427
23423
  },
23428
23424
  reasoning: {
23429
23425
  supported: false,
@@ -25106,7 +25102,9 @@ var AnthropicAdapter = class {
25106
25102
  if (req.thinking.mode === "off") {
25107
25103
  } else {
25108
25104
  const budget = req.thinking.effort ? ANTHROPIC_THINKING_BUDGETS[req.thinking.effort] ?? DEFAULT_ANTHROPIC_THINKING_BUDGET : DEFAULT_ANTHROPIC_THINKING_BUDGET;
25109
- body.thinking = { type: "enabled", budget_tokens: budget };
25105
+ const thinking = { type: "enabled", budget_tokens: budget };
25106
+ if (req.thinking.visibility === "hidden") thinking.display = "omitted";
25107
+ body.thinking = thinking;
25110
25108
  if (body.max_tokens <= budget) body.max_tokens = budget + 1024;
25111
25109
  }
25112
25110
  }
@@ -25623,6 +25621,21 @@ var GOOGLE_THINKING_LEVELS = {
25623
25621
  high: "HIGH",
25624
25622
  max: "HIGH"
25625
25623
  };
25624
+ var GOOGLE_THINKING_BUDGETS = {
25625
+ low: 2048,
25626
+ medium: 8192,
25627
+ high: 16384,
25628
+ max: 24576
25629
+ };
25630
+ function googleUsesThinkingBudget(model) {
25631
+ return /gemini-2\.5/.test(model);
25632
+ }
25633
+ var GOOGLE_INTERACTION_THINKING_LEVELS = {
25634
+ low: "low",
25635
+ medium: "medium",
25636
+ high: "high",
25637
+ max: "high"
25638
+ };
25626
25639
 
25627
25640
  // src/llm/providers/google/generate.ts
25628
25641
  var GoogleAdapter = class {
@@ -25703,9 +25716,16 @@ var GoogleAdapter = class {
25703
25716
  body.toolConfig = { functionCallingConfig: { mode } };
25704
25717
  }
25705
25718
  if (req.thinking && req.thinking.mode !== "off") {
25706
- config.thinkingConfig = {
25707
- thinkingLevel: GOOGLE_THINKING_LEVELS[req.thinking.effort ?? "high"] ?? "HIGH"
25719
+ const effort = req.thinking.effort ?? "high";
25720
+ const thinkingConfig = {
25721
+ includeThoughts: req.thinking.visibility !== "hidden"
25708
25722
  };
25723
+ if (googleUsesThinkingBudget(req.model)) {
25724
+ thinkingConfig.thinkingBudget = GOOGLE_THINKING_BUDGETS[effort] ?? GOOGLE_THINKING_BUDGETS.high;
25725
+ } else {
25726
+ thinkingConfig.thinkingLevel = GOOGLE_THINKING_LEVELS[effort] ?? "HIGH";
25727
+ }
25728
+ config.thinkingConfig = thinkingConfig;
25709
25729
  }
25710
25730
  if (req.structured) {
25711
25731
  config.responseMimeType = "application/json";
@@ -25721,6 +25741,9 @@ var GoogleAdapter = class {
25721
25741
  if (req.providerOptions.imageConfig) {
25722
25742
  config.imageConfig = req.providerOptions.imageConfig;
25723
25743
  }
25744
+ if (req.providerOptions.translationConfig) {
25745
+ config.translationConfig = req.providerOptions.translationConfig;
25746
+ }
25724
25747
  }
25725
25748
  return {
25726
25749
  body,
@@ -26071,8 +26094,6 @@ var GoogleInteractionsAdapter = class {
26071
26094
  if (req.maxTokens) genConfig.max_output_tokens = req.maxTokens;
26072
26095
  if (req.temperature !== void 0) genConfig.temperature = req.temperature;
26073
26096
  if (req.topP !== void 0) genConfig.top_p = req.topP;
26074
- if (req.presencePenalty !== void 0) genConfig.presence_penalty = req.presencePenalty;
26075
- if (req.frequencyPenalty !== void 0) genConfig.frequency_penalty = req.frequencyPenalty;
26076
26097
  if (req.stop) genConfig.stop_sequences = req.stop;
26077
26098
  if (req.tools?.length) {
26078
26099
  body.tools = req.tools.filter(isFunctionTool).map((t) => ({
@@ -26083,9 +26104,7 @@ var GoogleInteractionsAdapter = class {
26083
26104
  }));
26084
26105
  }
26085
26106
  if (req.thinking && req.thinking.mode !== "off") {
26086
- genConfig.thinking_config = {
26087
- thinking_level: GOOGLE_THINKING_LEVELS[req.thinking.effort ?? "high"] ?? "HIGH"
26088
- };
26107
+ genConfig.thinking_level = GOOGLE_INTERACTION_THINKING_LEVELS[req.thinking.effort ?? "high"] ?? "high";
26089
26108
  }
26090
26109
  if (Object.keys(genConfig).length > 0) body.generation_config = genConfig;
26091
26110
  const cachedContent = req.providerOptions?.cachedContent;
@@ -26388,6 +26407,24 @@ function openaiImageRef(ref) {
26388
26407
  function xaiImageRef(ref) {
26389
26408
  return ref.fileId ? { file_id: ref.fileId } : { url: toDataUrl(ref) };
26390
26409
  }
26410
+ function xaiVideoRef(src) {
26411
+ switch (src.type) {
26412
+ case "url":
26413
+ return { url: src.url };
26414
+ case "file":
26415
+ return { file_id: src.fileId };
26416
+ case "provider_ref":
26417
+ return { file_id: src.refId };
26418
+ case "base64":
26419
+ return { url: `data:${src.mimeType};base64,${src.data}` };
26420
+ case "buffer":
26421
+ return { url: `data:${src.mimeType};base64,${bytesToBase64(src.data)}` };
26422
+ case "path":
26423
+ throw new Error(
26424
+ "media source video: `path` DataSource is not supported here \u2014 read the file and pass base64/buffer."
26425
+ );
26426
+ }
26427
+ }
26391
26428
  function googleImagePart(ref) {
26392
26429
  const mimeType = ref.mimeType ?? "image/png";
26393
26430
  if (ref.base64) return { inline_data: { mime_type: mimeType, data: ref.base64 } };
@@ -26474,7 +26511,7 @@ var GoogleMediaAdapter = class {
26474
26511
  const image = {};
26475
26512
  if (req.params?.aspectRatio) image.aspectRatio = req.params.aspectRatio;
26476
26513
  if (req.params?.imageSize) image.imageSize = req.params.imageSize;
26477
- if (Object.keys(image).length) generationConfig.responseFormat = { image };
26514
+ if (Object.keys(image).length) generationConfig.imageConfig = image;
26478
26515
  const { items, usage } = await this.generateContentMedia(
26479
26516
  model,
26480
26517
  req.prompt,
@@ -26538,7 +26575,7 @@ var GoogleMediaAdapter = class {
26538
26575
  const image = {};
26539
26576
  if (req.params?.aspectRatio) image.aspectRatio = req.params.aspectRatio;
26540
26577
  if (req.params?.imageSize) image.imageSize = req.params.imageSize;
26541
- if (Object.keys(image).length) generationConfig.responseFormat = { image };
26578
+ if (Object.keys(image).length) generationConfig.imageConfig = image;
26542
26579
  const imagePart = googleImagePart(normalizeImageSource(req.sourceImage));
26543
26580
  const { items, usage } = await this.generateContentMedia(
26544
26581
  model,
@@ -26942,8 +26979,10 @@ var OpenAIBatchAdapter = class {
26942
26979
  };
26943
26980
 
26944
26981
  // src/llm/moderation/native.ts
26945
- function buildNativeModeration(mod) {
26946
- return { model: mod.model ?? MODERATION_DEFAULT_MODEL };
26982
+ function buildNativeModeration(mod, policy) {
26983
+ const out = { model: mod?.model ?? MODERATION_DEFAULT_MODEL };
26984
+ if (policy && typeof policy === "object") out.policy = policy;
26985
+ return out;
26947
26986
  }
26948
26987
  function parseNativeModeration(raw) {
26949
26988
  if (!raw || typeof raw !== "object") return void 0;
@@ -27053,8 +27092,12 @@ var OpenAIAdapter = class {
27053
27092
  if (req.stop) body.stop = req.stop;
27054
27093
  const tier = openaiRequestTier(req.serviceTier);
27055
27094
  if (tier) body.service_tier = tier;
27056
- if (req.moderation && req.moderation.mode !== "emulate") {
27057
- body.moderation = buildNativeModeration(req.moderation);
27095
+ const modPolicy = req.providerOptions?.moderationPolicy;
27096
+ if (req.moderation && req.moderation.mode !== "emulate" || modPolicy) {
27097
+ body.moderation = buildNativeModeration(req.moderation, modPolicy);
27098
+ }
27099
+ if (this.name === "openai" && req.providerOptions?.promptCacheOptions) {
27100
+ body.prompt_cache_options = req.providerOptions.promptCacheOptions;
27058
27101
  }
27059
27102
  const hasAudioInput = req.messages.some(
27060
27103
  (m) => Array.isArray(m.content) && m.content.some((p) => p.type === "audio")
@@ -27230,7 +27273,7 @@ var OpenAIAdapter = class {
27230
27273
  raw
27231
27274
  };
27232
27275
  }
27233
- parseStreamEvent(event) {
27276
+ parseStreamEvent(event, state) {
27234
27277
  const data = JSON.parse(event.data);
27235
27278
  if (data.moderation) {
27236
27279
  const report = parseNativeModeration(data.moderation);
@@ -27256,29 +27299,32 @@ var OpenAIAdapter = class {
27256
27299
  if (delta.content) {
27257
27300
  events.push({ type: "text", text: delta.content });
27258
27301
  }
27302
+ const toolIdByIndex = state?.toolIdByIndex ?? /* @__PURE__ */ new Map();
27259
27303
  const toolCalls = delta.tool_calls ?? [];
27260
27304
  for (const tc of toolCalls) {
27305
+ const index = tc.index ?? 0;
27306
+ let id = toolIdByIndex.get(index);
27307
+ if (id === void 0) {
27308
+ id = tc.id || `call_${crypto.randomUUID()}`;
27309
+ toolIdByIndex.set(index, id);
27310
+ }
27261
27311
  const fn = tc.function;
27262
27312
  if (fn?.name) {
27263
- events.push({
27264
- type: "tool_call_start",
27265
- id: tc.id ?? "",
27266
- name: fn.name
27267
- });
27313
+ events.push({ type: "tool_call_start", id, name: fn.name });
27268
27314
  }
27269
27315
  if (fn?.arguments) {
27270
- events.push({
27271
- type: "tool_call_delta",
27272
- id: tc.id ?? "",
27273
- arguments: fn.arguments
27274
- });
27316
+ events.push({ type: "tool_call_delta", id, arguments: fn.arguments });
27275
27317
  }
27276
27318
  }
27277
27319
  const fr = choice.finish_reason;
27278
27320
  if (fr) {
27279
27321
  events.push({
27280
27322
  type: "done",
27281
- finishReason: extractFinishReason(false, fr, { tool_calls: "tool_use", length: "length" })
27323
+ finishReason: extractFinishReason(false, fr, {
27324
+ tool_calls: "tool_use",
27325
+ length: "length",
27326
+ content_filter: "content_filter"
27327
+ })
27282
27328
  });
27283
27329
  }
27284
27330
  if (data.usage) {
@@ -27286,9 +27332,11 @@ var OpenAIAdapter = class {
27286
27332
  }
27287
27333
  return events;
27288
27334
  }
27289
- /** Stateless Chat Completions has no hosted code-execution file outputs. */
27335
+ /** Per-stream: correlates streamed tool-call fragments by index and synthesizes
27336
+ * a stable id for backends that omit tool-call ids (see `parseStreamEvent`). */
27290
27337
  createStreamParser() {
27291
- return (event) => this.parseStreamEvent(event);
27338
+ const state = { toolIdByIndex: /* @__PURE__ */ new Map() };
27339
+ return (event) => this.parseStreamEvent(event, state);
27292
27340
  }
27293
27341
  parseUsage(u) {
27294
27342
  if (!u) return emptyUsage();
@@ -27301,7 +27349,7 @@ var OpenAIAdapter = class {
27301
27349
  outputTokens: output,
27302
27350
  totalTokens: input + output,
27303
27351
  cachedTokens: details.cached_tokens ?? 0,
27304
- cacheWriteTokens: 0,
27352
+ cacheWriteTokens: details.cache_write_tokens ?? 0,
27305
27353
  reasoningTokens: outDetails.reasoning_tokens ?? 0
27306
27354
  };
27307
27355
  }
@@ -27947,8 +27995,12 @@ var OpenAIResponsesAdapter = class {
27947
27995
  if (req.topP !== void 0) body.top_p = req.topP;
27948
27996
  const tier = openaiRequestTier(req.serviceTier);
27949
27997
  if (tier) body.service_tier = tier;
27950
- if (req.moderation && req.moderation.mode !== "emulate") {
27951
- body.moderation = buildNativeModeration(req.moderation);
27998
+ const modPolicy = req.providerOptions?.moderationPolicy;
27999
+ if (req.moderation && req.moderation.mode !== "emulate" || modPolicy) {
28000
+ body.moderation = buildNativeModeration(req.moderation, modPolicy);
28001
+ }
28002
+ if (this.name === "openai" && req.providerOptions?.promptCacheOptions) {
28003
+ body.prompt_cache_options = req.providerOptions.promptCacheOptions;
27952
28004
  }
27953
28005
  if (req.tools?.length) {
27954
28006
  body.tools = req.tools.map((t) => {
@@ -27958,7 +28010,10 @@ var OpenAIResponsesAdapter = class {
27958
28010
  name: t.name,
27959
28011
  description: t.description,
27960
28012
  parameters: ensureAdditionalProperties(t.parameters),
27961
- strict: t.strict ?? true
28013
+ strict: t.strict ?? true,
28014
+ // Programmatic tool calling (Responses): who may call it + return schema.
28015
+ ...t.allowedCallers ? { allowed_callers: t.allowedCallers } : {},
28016
+ ...t.outputSchema ? { output_schema: t.outputSchema } : {}
27962
28017
  };
27963
28018
  }
27964
28019
  const builtin = { type: t.type, ...t.params };
@@ -27986,9 +28041,13 @@ var OpenAIResponsesAdapter = class {
27986
28041
  };
27987
28042
  }
27988
28043
  if (req.thinking && req.thinking.mode !== "off") {
28044
+ const visibility = req.thinking.visibility ?? "full";
28045
+ const summary = visibility === "hidden" ? null : visibility === "summary" ? "concise" : "auto";
28046
+ const mode = req.providerOptions?.reasoningMode;
27989
28047
  body.reasoning = {
27990
28048
  effort: req.thinking.effort ?? "medium",
27991
- summary: "auto",
28049
+ ...summary !== null ? { summary } : {},
28050
+ ...mode ? { mode } : {},
27992
28051
  // Cross-turn reasoning persistence (gpt-5/o-series, Responses only).
27993
28052
  ...req.thinking.context ? { context: req.thinking.context } : {}
27994
28053
  };
@@ -28132,9 +28191,8 @@ var OpenAIResponsesAdapter = class {
28132
28191
  }
28133
28192
  }
28134
28193
  const status = r.status;
28135
- const finishReason = extractFinishReason(toolCalls.length > 0, status, {
28136
- incomplete: "length"
28137
- });
28194
+ const incompleteReason = r.incomplete_details?.reason;
28195
+ const finishReason = incompleteReason === "content_filter" ? "content_filter" : extractFinishReason(toolCalls.length > 0, status, { incomplete: "length" });
28138
28196
  if (!text && typeof r.output_text === "string") {
28139
28197
  text = r.output_text;
28140
28198
  if (text && content.length === 0) content.push({ type: "text", text });
@@ -28254,7 +28312,7 @@ var OpenAIResponsesAdapter = class {
28254
28312
  outputTokens: output,
28255
28313
  totalTokens: u.total_tokens ?? input + output,
28256
28314
  cachedTokens: inputDetails.cached_tokens ?? 0,
28257
- cacheWriteTokens: 0,
28315
+ cacheWriteTokens: inputDetails.cache_write_tokens ?? 0,
28258
28316
  reasoningTokens: outputDetails.reasoning_tokens ?? 0
28259
28317
  };
28260
28318
  }
@@ -28344,8 +28402,9 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
28344
28402
  * `url_citation` annotations appear in the stream (the `:online` search signal). */
28345
28403
  createStreamParser() {
28346
28404
  let webSearchEmitted = false;
28405
+ const state = { toolIdByIndex: /* @__PURE__ */ new Map() };
28347
28406
  return (event) => {
28348
- const events = this.parseStreamEvent(event);
28407
+ const events = this.parseStreamEvent(event, state);
28349
28408
  if (!webSearchEmitted) {
28350
28409
  const choice = JSON.parse(event.data).choices?.[0];
28351
28410
  const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
@@ -28516,8 +28575,8 @@ var XAIAdapter = class extends OpenAIAdapter {
28516
28575
  }
28517
28576
  return result;
28518
28577
  }
28519
- parseStreamEvent(event) {
28520
- const events = super.parseStreamEvent(event);
28578
+ parseStreamEvent(event, state) {
28579
+ const events = super.parseStreamEvent(event, state);
28521
28580
  try {
28522
28581
  const data = JSON.parse(event.data);
28523
28582
  const choices = data.choices ?? [];
@@ -28640,7 +28699,8 @@ var XAIMediaAdapter = class {
28640
28699
  imageEditing: true,
28641
28700
  audioGeneration: true,
28642
28701
  videoGeneration: true,
28643
- audioStreaming: true
28702
+ audioStreaming: true,
28703
+ videoExtension: true
28644
28704
  };
28645
28705
  }
28646
28706
  authHeaders() {
@@ -28754,13 +28814,9 @@ var XAIMediaAdapter = class {
28754
28814
  }
28755
28815
  async submitVideo(req, fetch2) {
28756
28816
  const model = req.model ?? "grok-imagine-video";
28757
- const body = { model, prompt: req.prompt };
28758
- if (req.params?.duration) body.duration = req.params.duration;
28759
- if (req.params?.aspectRatio) body.aspect_ratio = req.params.aspectRatio;
28760
- if (req.params?.resolution) body.resolution = req.params.resolution;
28761
- if (req.sourceImage) body.image = xaiImageRef(normalizeImageSource(req.sourceImage));
28817
+ const { url, body } = this.buildVideoSubmit(req, model);
28762
28818
  const res = await fetch2({
28763
- url: `${this.baseURL}/v1/videos/generations`,
28819
+ url,
28764
28820
  method: "POST",
28765
28821
  headers: this.authHeaders(),
28766
28822
  body,
@@ -28771,6 +28827,30 @@ var XAIMediaAdapter = class {
28771
28827
  const data = res.body;
28772
28828
  return data.request_id ?? data.id ?? "";
28773
28829
  }
28830
+ /** Route a video request to the right xAI endpoint by input + mode:
28831
+ * - no `sourceVideo` → `/v1/videos/generations` (text/image-to-video)
28832
+ * - `sourceVideo` + `videoMode:'extend'` (default) → `/v1/videos/extensions`
28833
+ * — continues from the last frame; takes `duration`, NOT aspect/resolution.
28834
+ * - `sourceVideo` + `videoMode:'edit'` → `/v1/videos/edits` — prompt + video
28835
+ * only (no duration/aspect/resolution).
28836
+ * All three return a `request_id` polled via the same status endpoint. */
28837
+ buildVideoSubmit(req, model) {
28838
+ if (req.sourceVideo) {
28839
+ const video = xaiVideoRef(req.sourceVideo);
28840
+ if ((req.params?.videoMode ?? "extend") === "edit") {
28841
+ return { url: `${this.baseURL}/v1/videos/edits`, body: { model, prompt: req.prompt, video } };
28842
+ }
28843
+ const body2 = { model, prompt: req.prompt, video };
28844
+ if (req.params?.duration) body2.duration = req.params.duration;
28845
+ return { url: `${this.baseURL}/v1/videos/extensions`, body: body2 };
28846
+ }
28847
+ const body = { model, prompt: req.prompt };
28848
+ if (req.params?.duration) body.duration = req.params.duration;
28849
+ if (req.params?.aspectRatio) body.aspect_ratio = req.params.aspectRatio;
28850
+ if (req.params?.resolution) body.resolution = req.params.resolution;
28851
+ if (req.sourceImage) body.image = xaiImageRef(normalizeImageSource(req.sourceImage));
28852
+ return { url: `${this.baseURL}/v1/videos/generations`, body };
28853
+ }
28774
28854
  async getVideoStatus(operationId, fetch2) {
28775
28855
  const res = await fetch2({
28776
28856
  url: `${this.baseURL}/v1/videos/${operationId}`,
@@ -28784,13 +28864,15 @@ var XAIMediaAdapter = class {
28784
28864
  if (res.status >= 400) return { status: "failed", error: `HTTP ${res.status}` };
28785
28865
  const data = res.body;
28786
28866
  const state = data.status ?? "";
28787
- if (state === "completed" || state === "ready" || data.download_url) {
28788
- return { status: "completed" };
28867
+ const video = data.video;
28868
+ const progress = data.progress;
28869
+ if (state === "done" || state === "completed" || state === "ready" || video?.url || data.download_url) {
28870
+ return { status: "completed", progress };
28789
28871
  }
28790
- if (state === "failed" || state === "error") {
28872
+ if (state === "failed" || state === "error" || state === "expired") {
28791
28873
  return { status: "failed", error: data.error ?? "Unknown error" };
28792
28874
  }
28793
- return { status: "processing", progress: data.progress };
28875
+ return { status: "processing", progress };
28794
28876
  }
28795
28877
  async downloadVideo(operationId, fetch2) {
28796
28878
  const statusRes = await fetch2({
@@ -28806,8 +28888,19 @@ var XAIMediaAdapter = class {
28806
28888
  throw new Error(`xAI video download failed: HTTP ${statusRes.status}`);
28807
28889
  }
28808
28890
  const data = statusRes.body;
28809
- const downloadUrl = data.download_url ?? data.url;
28891
+ const video = data.video;
28892
+ const downloadUrl = video?.url ?? data.download_url ?? data.url;
28810
28893
  if (!downloadUrl) throw new Error("No download URL in video response");
28894
+ const durationSec = video?.duration ?? data.duration;
28895
+ const base = {
28896
+ data: new Uint8Array(0),
28897
+ mimeType: "video/mp4",
28898
+ sourceUrl: downloadUrl,
28899
+ durationMs: durationSec ? durationSec * 1e3 : void 0,
28900
+ // Provider-reported cost (usage.cost_in_usd_ticks), when present.
28901
+ providerMeta: data.usage ? { usage: data.usage } : void 0
28902
+ };
28903
+ if (isBrowser()) return base;
28811
28904
  const videoRes = await fetch2({
28812
28905
  url: downloadUrl,
28813
28906
  method: "GET",
@@ -28817,13 +28910,7 @@ var XAIMediaAdapter = class {
28817
28910
  model: "",
28818
28911
  responseType: "arraybuffer"
28819
28912
  });
28820
- return {
28821
- data: videoRes.body,
28822
- mimeType: "video/mp4",
28823
- durationMs: data.duration ? data.duration * 1e3 : void 0,
28824
- // Provider-reported cost (usage.cost_in_usd_ticks), when present.
28825
- providerMeta: data.usage ? { usage: data.usage } : void 0
28826
- };
28913
+ return { ...base, data: videoRes.body };
28827
28914
  }
28828
28915
  async cancelVideo(operationId, fetch2) {
28829
28916
  await fetch2({
@@ -30236,7 +30323,11 @@ var AgentLoop = class _AgentLoop {
30236
30323
  id: lastResponse?.id ?? `agent-${runId}`,
30237
30324
  model: this.client.model,
30238
30325
  content: finalContent,
30239
- finishReason: reason === "done" ? "stop" : reason === "stopped" ? "stop" : reason === "guardrail" ? "stop" : reason === "max_steps" ? "length" : "error",
30326
+ finishReason: reason === "done" ? (
30327
+ // Ended because the model requested no tools — surface the provider's
30328
+ // actual reason (stop / content_filter / length), not a flat 'stop'.
30329
+ lastResponse?.finishReason ?? "stop"
30330
+ ) : reason === "stopped" ? "stop" : reason === "guardrail" ? "stop" : reason === "max_steps" ? "length" : "error",
30240
30331
  usage: totalUsage,
30241
30332
  text: finalText,
30242
30333
  toolCalls: lastResponse?.toolCalls ?? [],
@@ -30445,7 +30536,11 @@ var AgentLoop = class _AgentLoop {
30445
30536
  id: lastResponse?.id ?? `agent-${runId}`,
30446
30537
  model: this.client.model,
30447
30538
  content: finalContent,
30448
- finishReason: reason === "done" ? "stop" : reason === "stopped" ? "stop" : reason === "guardrail" ? "stop" : reason === "max_steps" ? "length" : "error",
30539
+ finishReason: reason === "done" ? (
30540
+ // Ended because the model requested no tools — surface the provider's
30541
+ // actual reason (stop / content_filter / length), not a flat 'stop'.
30542
+ lastResponse?.finishReason ?? "stop"
30543
+ ) : reason === "stopped" ? "stop" : reason === "guardrail" ? "stop" : reason === "max_steps" ? "length" : "error",
30449
30544
  usage: totalUsage,
30450
30545
  text: finalText,
30451
30546
  toolCalls: lastResponse?.toolCalls ?? [],
@@ -34770,9 +34865,13 @@ var MediaOutput = class {
34770
34865
  }
34771
34866
  async generateVideo(req) {
34772
34867
  const adapter = this.getAdapter(req.provider);
34773
- if (!adapter.capabilities().videoGeneration || !adapter.submitVideo) {
34868
+ const caps = adapter.capabilities();
34869
+ if (!caps.videoGeneration || !adapter.submitVideo) {
34774
34870
  throw new Error(`Provider ${req.provider} does not support video generation`);
34775
34871
  }
34872
+ if (req.sourceVideo && !caps.videoExtension) {
34873
+ throw new Error(`Provider ${req.provider} does not support video extension/editing`);
34874
+ }
34776
34875
  const { trace, fetch: fetch2 } = this.tracedOp();
34777
34876
  const operationId = await adapter.submitVideo(req, fetch2);
34778
34877
  return this.pollVideoCompletion(adapter, operationId, req, fetch2, trace);
@@ -34805,7 +34904,8 @@ var MediaOutput = class {
34805
34904
  width: raw.width,
34806
34905
  height: raw.height,
34807
34906
  durationMs: raw.durationMs,
34808
- sampleRate: raw.sampleRate
34907
+ sampleRate: raw.sampleRate,
34908
+ sourceUrl: raw.sourceUrl
34809
34909
  };
34810
34910
  await this.mediaStore.save(id, raw.data, meta);
34811
34911
  results.push({ id, type, mimeType: raw.mimeType, meta });
@@ -34838,6 +34938,15 @@ var MediaOutput = class {
34838
34938
  const start = Date.now();
34839
34939
  while (Date.now() - start < this.maxPollWaitMs) {
34840
34940
  const status = await adapter.getVideoStatus(operationId, fetch2);
34941
+ if (status.status === "processing" || status.status === "pending") {
34942
+ await this.hooks.emit("onMediaProgress", {
34943
+ type: "video",
34944
+ provider: req.provider,
34945
+ operationId,
34946
+ progress: status.progress,
34947
+ model: req.model
34948
+ });
34949
+ }
34841
34950
  if (status.status === "completed") {
34842
34951
  const raw = await adapter.downloadVideo(operationId, fetch2);
34843
34952
  const results = await this.saveResults(
@@ -9,10 +9,11 @@
9
9
  * - Chat Completions: moderation.{input,output} = moderation_results | error,
10
10
  * where moderation_results wraps `results: [moderation_result]`. */
11
11
  import type { ModerationReport, ModerationRequest } from './types';
12
- /** The `moderation` request field for the OpenAI native path. */
13
- export declare function buildNativeModeration(mod: ModerationRequest): {
14
- model: string;
15
- };
12
+ /** The `moderation` request field for the OpenAI native path. `policy` is an
13
+ * OpenAI-only opt-in (via `providerOptions.moderationPolicy`) for server-side
14
+ * BLOCKING — `{ input?: { mode: 'score'|'block' }, output?: {...} }`; our unified
15
+ * moderation stays report-only, so it's a passthrough, not a first-class knob. */
16
+ export declare function buildNativeModeration(mod?: ModerationRequest, policy?: unknown): Record<string, unknown>;
16
17
  /** Parse OpenAI's returned `moderation` object into a unified report, or undefined
17
18
  * when the server returned nothing usable. */
18
19
  export declare function parseNativeModeration(raw: unknown): ModerationReport | undefined;
@@ -1,6 +1,21 @@
1
1
  /** Google provider constants. */
2
2
  /**
3
- * Map from unified thinking effort levels to Gemini thinkingLevel enum strings.
4
- * Used in both generate.ts and interactions.ts.
3
+ * Map from unified thinking effort levels to Gemini `thinkingLevel` enum strings.
4
+ * `thinkingLevel` is the Gemini 3.x thinking control (LOW/HIGH).
5
5
  */
6
6
  export declare const GOOGLE_THINKING_LEVELS: Record<string, string>;
7
+ /**
8
+ * Map from unified thinking effort to a Gemini `thinkingBudget` (token count).
9
+ * Gemini **2.5** models only accept a token budget — they 400 on `thinkingLevel`
10
+ * ("Thinking level is not supported for this model", live-verified 2026-07-16).
11
+ * Values sit inside the 2.5 range (flash/flash-lite cap ~24576, pro ~32768).
12
+ */
13
+ export declare const GOOGLE_THINKING_BUDGETS: Record<string, number>;
14
+ /** Gemini 2.5 series uses `thinkingBudget`; 3.x+ uses `thinkingLevel`. */
15
+ export declare function googleUsesThinkingBudget(model: string): boolean;
16
+ /**
17
+ * Effort → Interactions `thinking_level`. The Interactions API uses **lowercase**
18
+ * values (`minimal`/`low`/`medium`/`high`) — distinct from generateContent's
19
+ * uppercase `thinkingLevel`, and it 400s on the uppercase form (live 2026-07-16).
20
+ */
21
+ export declare const GOOGLE_INTERACTION_THINKING_LEVELS: Record<string, string>;
@@ -8,6 +8,12 @@ export interface OpenAIAdapterConfig {
8
8
  apiKey: string;
9
9
  baseURL?: string;
10
10
  }
11
+ /** Per-stream state threaded through `createStreamParser` — maps a streamed
12
+ * tool call's `index` to its resolved id (real, or a synthesized `call_<uuid>`
13
+ * for backends that omit ids), stable across the stream's chunks. */
14
+ export interface OpenAIStreamState {
15
+ toolIdByIndex: Map<number, string>;
16
+ }
11
17
  export declare class OpenAIAdapter implements ProviderAdapter {
12
18
  readonly name: ProviderAdapter['name'];
13
19
  protected readonly apiKey: string;
@@ -20,8 +26,9 @@ export declare class OpenAIAdapter implements ProviderAdapter {
20
26
  private buildMessage;
21
27
  enableStreaming(providerReq: ProviderHttpRequest, _req: NormalizedRequest): void;
22
28
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
23
- parseStreamEvent(event: SSEEvent): StreamEvent[];
24
- /** Stateless Chat Completions has no hosted code-execution file outputs. */
29
+ parseStreamEvent(event: SSEEvent, state?: OpenAIStreamState): StreamEvent[];
30
+ /** Per-stream: correlates streamed tool-call fragments by index and synthesizes
31
+ * a stable id for backends that omit tool-call ids (see `parseStreamEvent`). */
25
32
  createStreamParser(): (event: SSEEvent) => StreamEvent[];
26
33
  private parseUsage;
27
34
  }
@@ -9,7 +9,7 @@ import type { ProviderAdapter, ProviderHttpRequest } from '../../types/provider'
9
9
  import type { NormalizedRequest } from '../../types/request';
10
10
  import type { CompletionResponse } from '../../types/response';
11
11
  import type { StreamEvent } from '../../types/stream';
12
- import { OpenAIAdapter } from '../openai/completions';
12
+ import { OpenAIAdapter, type OpenAIStreamState } from '../openai/completions';
13
13
  export interface XAIAdapterConfig {
14
14
  apiKey: string;
15
15
  baseURL?: string;
@@ -20,5 +20,5 @@ export declare class XAIAdapter extends OpenAIAdapter {
20
20
  baseURL(): string;
21
21
  buildRequest(req: NormalizedRequest): ProviderHttpRequest;
22
22
  parseResponse(raw: unknown, latencyMs: number): CompletionResponse;
23
- parseStreamEvent(event: SSEEvent): StreamEvent[];
23
+ parseStreamEvent(event: SSEEvent, state?: OpenAIStreamState): StreamEvent[];
24
24
  }
@@ -21,6 +21,14 @@ export declare class XAIMediaAdapter implements MediaProviderAdapter {
21
21
  private parseImages;
22
22
  generateAudio(req: AudioGenRequest, fetch: EngineFetch): Promise<RawMediaResult>;
23
23
  submitVideo(req: VideoGenRequest, fetch: EngineFetch): Promise<string>;
24
+ /** Route a video request to the right xAI endpoint by input + mode:
25
+ * - no `sourceVideo` → `/v1/videos/generations` (text/image-to-video)
26
+ * - `sourceVideo` + `videoMode:'extend'` (default) → `/v1/videos/extensions`
27
+ * — continues from the last frame; takes `duration`, NOT aspect/resolution.
28
+ * - `sourceVideo` + `videoMode:'edit'` → `/v1/videos/edits` — prompt + video
29
+ * only (no duration/aspect/resolution).
30
+ * All three return a `request_id` polled via the same status endpoint. */
31
+ private buildVideoSubmit;
24
32
  getVideoStatus(operationId: string, fetch: EngineFetch): Promise<VideoStatus>;
25
33
  downloadVideo(operationId: string, fetch: EngineFetch): Promise<RawMediaResult>;
26
34
  cancelVideo(operationId: string, fetch: EngineFetch): Promise<void>;
@@ -49,13 +49,21 @@ export interface NormalizedRequest {
49
49
  * token cost; `current_turn` drops earlier reasoning; `auto` lets OpenAI decide.
50
50
  * Ignored by every other provider. */
51
51
  export type ReasoningContext = 'auto' | 'current_turn' | 'all_turns';
52
+ /** How much of the model's reasoning is returned. `full` (default) returns it as
53
+ * fully as the provider allows; `summary` a condensed form where the provider
54
+ * supports one (else full); `hidden` keeps reasoning internal. Best-effort per
55
+ * provider — Anthropic `enabled.display`, OpenAI Responses `summary`, Google
56
+ * `includeThoughts`; providers without a control ignore it. */
57
+ export type ThinkingVisibility = 'full' | 'summary' | 'hidden';
52
58
  export type ThinkingConfig = {
53
59
  mode: 'auto';
54
60
  effort?: 'low' | 'medium' | 'high' | 'max';
61
+ visibility?: ThinkingVisibility;
55
62
  context?: ReasoningContext;
56
63
  } | {
57
64
  mode: 'on';
58
65
  effort?: 'low' | 'medium' | 'high' | 'max';
66
+ visibility?: ThinkingVisibility;
59
67
  context?: ReasoningContext;
60
68
  } | {
61
69
  mode: 'off';
@@ -6,9 +6,18 @@ export interface FunctionTool {
6
6
  parameters: JsonSchema;
7
7
  strict?: boolean;
8
8
  cache?: boolean;
9
+ /** OpenAI **Responses** programmatic tool calling: which callers may invoke this
10
+ * tool — `direct` (the model calls it) and/or `programmatic` (generated
11
+ * orchestration code calls it). OpenAI-Responses-only; ignored elsewhere. */
12
+ allowedCallers?: Array<'direct' | 'programmatic'>;
13
+ /** OpenAI **Responses** JSON schema for the tool's return value (lets the model
14
+ * reason over structured tool output). OpenAI-Responses-only. */
15
+ outputSchema?: JsonSchema;
9
16
  }
10
17
  export interface BuiltinTool {
11
- type: 'image_generation' | 'web_search' | 'web_fetch' | 'code_interpreter' | 'file_search' | 'mcp';
18
+ type: 'image_generation' | 'web_search' | 'web_fetch' | 'code_interpreter' | 'file_search' | 'mcp'
19
+ /** OpenAI Responses: lets the model write JS to orchestrate tool calls. */
20
+ | 'programmatic_tool_calling';
12
21
  params?: Record<string, unknown>;
13
22
  }
14
23
  /** Typed shape for an `mcp` builtin's `params` (OpenAI hosted MCP tool). The