@combycode/llm-sdk 1.6.1 → 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
@@ -23418,7 +23418,8 @@ var catalog_default5 = {
23418
23418
  video: false,
23419
23419
  imageGeneration: false,
23420
23420
  audioGeneration: false,
23421
- videoGeneration: true
23421
+ videoGeneration: true,
23422
+ videoExtension: true
23422
23423
  },
23423
23424
  reasoning: {
23424
23425
  supported: false,
@@ -25101,7 +25102,9 @@ var AnthropicAdapter = class {
25101
25102
  if (req.thinking.mode === "off") {
25102
25103
  } else {
25103
25104
  const budget = req.thinking.effort ? ANTHROPIC_THINKING_BUDGETS[req.thinking.effort] ?? DEFAULT_ANTHROPIC_THINKING_BUDGET : DEFAULT_ANTHROPIC_THINKING_BUDGET;
25104
- 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;
25105
25108
  if (body.max_tokens <= budget) body.max_tokens = budget + 1024;
25106
25109
  }
25107
25110
  }
@@ -25618,6 +25621,21 @@ var GOOGLE_THINKING_LEVELS = {
25618
25621
  high: "HIGH",
25619
25622
  max: "HIGH"
25620
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
+ };
25621
25639
 
25622
25640
  // src/llm/providers/google/generate.ts
25623
25641
  var GoogleAdapter = class {
@@ -25698,9 +25716,16 @@ var GoogleAdapter = class {
25698
25716
  body.toolConfig = { functionCallingConfig: { mode } };
25699
25717
  }
25700
25718
  if (req.thinking && req.thinking.mode !== "off") {
25701
- config.thinkingConfig = {
25702
- 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"
25703
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;
25704
25729
  }
25705
25730
  if (req.structured) {
25706
25731
  config.responseMimeType = "application/json";
@@ -25716,6 +25741,9 @@ var GoogleAdapter = class {
25716
25741
  if (req.providerOptions.imageConfig) {
25717
25742
  config.imageConfig = req.providerOptions.imageConfig;
25718
25743
  }
25744
+ if (req.providerOptions.translationConfig) {
25745
+ config.translationConfig = req.providerOptions.translationConfig;
25746
+ }
25719
25747
  }
25720
25748
  return {
25721
25749
  body,
@@ -26066,8 +26094,6 @@ var GoogleInteractionsAdapter = class {
26066
26094
  if (req.maxTokens) genConfig.max_output_tokens = req.maxTokens;
26067
26095
  if (req.temperature !== void 0) genConfig.temperature = req.temperature;
26068
26096
  if (req.topP !== void 0) genConfig.top_p = req.topP;
26069
- if (req.presencePenalty !== void 0) genConfig.presence_penalty = req.presencePenalty;
26070
- if (req.frequencyPenalty !== void 0) genConfig.frequency_penalty = req.frequencyPenalty;
26071
26097
  if (req.stop) genConfig.stop_sequences = req.stop;
26072
26098
  if (req.tools?.length) {
26073
26099
  body.tools = req.tools.filter(isFunctionTool).map((t) => ({
@@ -26078,9 +26104,7 @@ var GoogleInteractionsAdapter = class {
26078
26104
  }));
26079
26105
  }
26080
26106
  if (req.thinking && req.thinking.mode !== "off") {
26081
- genConfig.thinking_config = {
26082
- thinking_level: GOOGLE_THINKING_LEVELS[req.thinking.effort ?? "high"] ?? "HIGH"
26083
- };
26107
+ genConfig.thinking_level = GOOGLE_INTERACTION_THINKING_LEVELS[req.thinking.effort ?? "high"] ?? "high";
26084
26108
  }
26085
26109
  if (Object.keys(genConfig).length > 0) body.generation_config = genConfig;
26086
26110
  const cachedContent = req.providerOptions?.cachedContent;
@@ -26383,6 +26407,24 @@ function openaiImageRef(ref) {
26383
26407
  function xaiImageRef(ref) {
26384
26408
  return ref.fileId ? { file_id: ref.fileId } : { url: toDataUrl(ref) };
26385
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
+ }
26386
26428
  function googleImagePart(ref) {
26387
26429
  const mimeType = ref.mimeType ?? "image/png";
26388
26430
  if (ref.base64) return { inline_data: { mime_type: mimeType, data: ref.base64 } };
@@ -26533,7 +26575,7 @@ var GoogleMediaAdapter = class {
26533
26575
  const image = {};
26534
26576
  if (req.params?.aspectRatio) image.aspectRatio = req.params.aspectRatio;
26535
26577
  if (req.params?.imageSize) image.imageSize = req.params.imageSize;
26536
- if (Object.keys(image).length) generationConfig.responseFormat = { image };
26578
+ if (Object.keys(image).length) generationConfig.imageConfig = image;
26537
26579
  const imagePart = googleImagePart(normalizeImageSource(req.sourceImage));
26538
26580
  const { items, usage } = await this.generateContentMedia(
26539
26581
  model,
@@ -26937,8 +26979,10 @@ var OpenAIBatchAdapter = class {
26937
26979
  };
26938
26980
 
26939
26981
  // src/llm/moderation/native.ts
26940
- function buildNativeModeration(mod) {
26941
- 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;
26942
26986
  }
26943
26987
  function parseNativeModeration(raw) {
26944
26988
  if (!raw || typeof raw !== "object") return void 0;
@@ -27048,8 +27092,12 @@ var OpenAIAdapter = class {
27048
27092
  if (req.stop) body.stop = req.stop;
27049
27093
  const tier = openaiRequestTier(req.serviceTier);
27050
27094
  if (tier) body.service_tier = tier;
27051
- if (req.moderation && req.moderation.mode !== "emulate") {
27052
- 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;
27053
27101
  }
27054
27102
  const hasAudioInput = req.messages.some(
27055
27103
  (m) => Array.isArray(m.content) && m.content.some((p) => p.type === "audio")
@@ -27225,7 +27273,7 @@ var OpenAIAdapter = class {
27225
27273
  raw
27226
27274
  };
27227
27275
  }
27228
- parseStreamEvent(event) {
27276
+ parseStreamEvent(event, state) {
27229
27277
  const data = JSON.parse(event.data);
27230
27278
  if (data.moderation) {
27231
27279
  const report = parseNativeModeration(data.moderation);
@@ -27251,29 +27299,32 @@ var OpenAIAdapter = class {
27251
27299
  if (delta.content) {
27252
27300
  events.push({ type: "text", text: delta.content });
27253
27301
  }
27302
+ const toolIdByIndex = state?.toolIdByIndex ?? /* @__PURE__ */ new Map();
27254
27303
  const toolCalls = delta.tool_calls ?? [];
27255
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
+ }
27256
27311
  const fn = tc.function;
27257
27312
  if (fn?.name) {
27258
- events.push({
27259
- type: "tool_call_start",
27260
- id: tc.id ?? "",
27261
- name: fn.name
27262
- });
27313
+ events.push({ type: "tool_call_start", id, name: fn.name });
27263
27314
  }
27264
27315
  if (fn?.arguments) {
27265
- events.push({
27266
- type: "tool_call_delta",
27267
- id: tc.id ?? "",
27268
- arguments: fn.arguments
27269
- });
27316
+ events.push({ type: "tool_call_delta", id, arguments: fn.arguments });
27270
27317
  }
27271
27318
  }
27272
27319
  const fr = choice.finish_reason;
27273
27320
  if (fr) {
27274
27321
  events.push({
27275
27322
  type: "done",
27276
- 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
+ })
27277
27328
  });
27278
27329
  }
27279
27330
  if (data.usage) {
@@ -27281,9 +27332,11 @@ var OpenAIAdapter = class {
27281
27332
  }
27282
27333
  return events;
27283
27334
  }
27284
- /** 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`). */
27285
27337
  createStreamParser() {
27286
- return (event) => this.parseStreamEvent(event);
27338
+ const state = { toolIdByIndex: /* @__PURE__ */ new Map() };
27339
+ return (event) => this.parseStreamEvent(event, state);
27287
27340
  }
27288
27341
  parseUsage(u) {
27289
27342
  if (!u) return emptyUsage();
@@ -27296,7 +27349,7 @@ var OpenAIAdapter = class {
27296
27349
  outputTokens: output,
27297
27350
  totalTokens: input + output,
27298
27351
  cachedTokens: details.cached_tokens ?? 0,
27299
- cacheWriteTokens: 0,
27352
+ cacheWriteTokens: details.cache_write_tokens ?? 0,
27300
27353
  reasoningTokens: outDetails.reasoning_tokens ?? 0
27301
27354
  };
27302
27355
  }
@@ -27942,8 +27995,12 @@ var OpenAIResponsesAdapter = class {
27942
27995
  if (req.topP !== void 0) body.top_p = req.topP;
27943
27996
  const tier = openaiRequestTier(req.serviceTier);
27944
27997
  if (tier) body.service_tier = tier;
27945
- if (req.moderation && req.moderation.mode !== "emulate") {
27946
- 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;
27947
28004
  }
27948
28005
  if (req.tools?.length) {
27949
28006
  body.tools = req.tools.map((t) => {
@@ -27953,7 +28010,10 @@ var OpenAIResponsesAdapter = class {
27953
28010
  name: t.name,
27954
28011
  description: t.description,
27955
28012
  parameters: ensureAdditionalProperties(t.parameters),
27956
- 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 } : {}
27957
28017
  };
27958
28018
  }
27959
28019
  const builtin = { type: t.type, ...t.params };
@@ -27981,9 +28041,13 @@ var OpenAIResponsesAdapter = class {
27981
28041
  };
27982
28042
  }
27983
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;
27984
28047
  body.reasoning = {
27985
28048
  effort: req.thinking.effort ?? "medium",
27986
- summary: "auto",
28049
+ ...summary !== null ? { summary } : {},
28050
+ ...mode ? { mode } : {},
27987
28051
  // Cross-turn reasoning persistence (gpt-5/o-series, Responses only).
27988
28052
  ...req.thinking.context ? { context: req.thinking.context } : {}
27989
28053
  };
@@ -28127,9 +28191,8 @@ var OpenAIResponsesAdapter = class {
28127
28191
  }
28128
28192
  }
28129
28193
  const status = r.status;
28130
- const finishReason = extractFinishReason(toolCalls.length > 0, status, {
28131
- incomplete: "length"
28132
- });
28194
+ const incompleteReason = r.incomplete_details?.reason;
28195
+ const finishReason = incompleteReason === "content_filter" ? "content_filter" : extractFinishReason(toolCalls.length > 0, status, { incomplete: "length" });
28133
28196
  if (!text && typeof r.output_text === "string") {
28134
28197
  text = r.output_text;
28135
28198
  if (text && content.length === 0) content.push({ type: "text", text });
@@ -28249,7 +28312,7 @@ var OpenAIResponsesAdapter = class {
28249
28312
  outputTokens: output,
28250
28313
  totalTokens: u.total_tokens ?? input + output,
28251
28314
  cachedTokens: inputDetails.cached_tokens ?? 0,
28252
- cacheWriteTokens: 0,
28315
+ cacheWriteTokens: inputDetails.cache_write_tokens ?? 0,
28253
28316
  reasoningTokens: outputDetails.reasoning_tokens ?? 0
28254
28317
  };
28255
28318
  }
@@ -28339,8 +28402,9 @@ var OpenRouterAdapter = class extends OpenAIAdapter {
28339
28402
  * `url_citation` annotations appear in the stream (the `:online` search signal). */
28340
28403
  createStreamParser() {
28341
28404
  let webSearchEmitted = false;
28405
+ const state = { toolIdByIndex: /* @__PURE__ */ new Map() };
28342
28406
  return (event) => {
28343
- const events = this.parseStreamEvent(event);
28407
+ const events = this.parseStreamEvent(event, state);
28344
28408
  if (!webSearchEmitted) {
28345
28409
  const choice = JSON.parse(event.data).choices?.[0];
28346
28410
  const annotations = choice?.delta?.annotations ?? choice?.message?.annotations;
@@ -28511,8 +28575,8 @@ var XAIAdapter = class extends OpenAIAdapter {
28511
28575
  }
28512
28576
  return result;
28513
28577
  }
28514
- parseStreamEvent(event) {
28515
- const events = super.parseStreamEvent(event);
28578
+ parseStreamEvent(event, state) {
28579
+ const events = super.parseStreamEvent(event, state);
28516
28580
  try {
28517
28581
  const data = JSON.parse(event.data);
28518
28582
  const choices = data.choices ?? [];
@@ -28635,7 +28699,8 @@ var XAIMediaAdapter = class {
28635
28699
  imageEditing: true,
28636
28700
  audioGeneration: true,
28637
28701
  videoGeneration: true,
28638
- audioStreaming: true
28702
+ audioStreaming: true,
28703
+ videoExtension: true
28639
28704
  };
28640
28705
  }
28641
28706
  authHeaders() {
@@ -28749,13 +28814,9 @@ var XAIMediaAdapter = class {
28749
28814
  }
28750
28815
  async submitVideo(req, fetch2) {
28751
28816
  const model = req.model ?? "grok-imagine-video";
28752
- const body = { model, prompt: req.prompt };
28753
- if (req.params?.duration) body.duration = req.params.duration;
28754
- if (req.params?.aspectRatio) body.aspect_ratio = req.params.aspectRatio;
28755
- if (req.params?.resolution) body.resolution = req.params.resolution;
28756
- if (req.sourceImage) body.image = xaiImageRef(normalizeImageSource(req.sourceImage));
28817
+ const { url, body } = this.buildVideoSubmit(req, model);
28757
28818
  const res = await fetch2({
28758
- url: `${this.baseURL}/v1/videos/generations`,
28819
+ url,
28759
28820
  method: "POST",
28760
28821
  headers: this.authHeaders(),
28761
28822
  body,
@@ -28766,6 +28827,30 @@ var XAIMediaAdapter = class {
28766
28827
  const data = res.body;
28767
28828
  return data.request_id ?? data.id ?? "";
28768
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
+ }
28769
28854
  async getVideoStatus(operationId, fetch2) {
28770
28855
  const res = await fetch2({
28771
28856
  url: `${this.baseURL}/v1/videos/${operationId}`,
@@ -28779,13 +28864,15 @@ var XAIMediaAdapter = class {
28779
28864
  if (res.status >= 400) return { status: "failed", error: `HTTP ${res.status}` };
28780
28865
  const data = res.body;
28781
28866
  const state = data.status ?? "";
28782
- if (state === "completed" || state === "ready" || data.download_url) {
28783
- 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 };
28784
28871
  }
28785
- if (state === "failed" || state === "error") {
28872
+ if (state === "failed" || state === "error" || state === "expired") {
28786
28873
  return { status: "failed", error: data.error ?? "Unknown error" };
28787
28874
  }
28788
- return { status: "processing", progress: data.progress };
28875
+ return { status: "processing", progress };
28789
28876
  }
28790
28877
  async downloadVideo(operationId, fetch2) {
28791
28878
  const statusRes = await fetch2({
@@ -28801,8 +28888,19 @@ var XAIMediaAdapter = class {
28801
28888
  throw new Error(`xAI video download failed: HTTP ${statusRes.status}`);
28802
28889
  }
28803
28890
  const data = statusRes.body;
28804
- const downloadUrl = data.download_url ?? data.url;
28891
+ const video = data.video;
28892
+ const downloadUrl = video?.url ?? data.download_url ?? data.url;
28805
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;
28806
28904
  const videoRes = await fetch2({
28807
28905
  url: downloadUrl,
28808
28906
  method: "GET",
@@ -28812,13 +28910,7 @@ var XAIMediaAdapter = class {
28812
28910
  model: "",
28813
28911
  responseType: "arraybuffer"
28814
28912
  });
28815
- return {
28816
- data: videoRes.body,
28817
- mimeType: "video/mp4",
28818
- durationMs: data.duration ? data.duration * 1e3 : void 0,
28819
- // Provider-reported cost (usage.cost_in_usd_ticks), when present.
28820
- providerMeta: data.usage ? { usage: data.usage } : void 0
28821
- };
28913
+ return { ...base, data: videoRes.body };
28822
28914
  }
28823
28915
  async cancelVideo(operationId, fetch2) {
28824
28916
  await fetch2({
@@ -30231,7 +30323,11 @@ var AgentLoop = class _AgentLoop {
30231
30323
  id: lastResponse?.id ?? `agent-${runId}`,
30232
30324
  model: this.client.model,
30233
30325
  content: finalContent,
30234
- 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",
30235
30331
  usage: totalUsage,
30236
30332
  text: finalText,
30237
30333
  toolCalls: lastResponse?.toolCalls ?? [],
@@ -30440,7 +30536,11 @@ var AgentLoop = class _AgentLoop {
30440
30536
  id: lastResponse?.id ?? `agent-${runId}`,
30441
30537
  model: this.client.model,
30442
30538
  content: finalContent,
30443
- 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",
30444
30544
  usage: totalUsage,
30445
30545
  text: finalText,
30446
30546
  toolCalls: lastResponse?.toolCalls ?? [],
@@ -34765,9 +34865,13 @@ var MediaOutput = class {
34765
34865
  }
34766
34866
  async generateVideo(req) {
34767
34867
  const adapter = this.getAdapter(req.provider);
34768
- if (!adapter.capabilities().videoGeneration || !adapter.submitVideo) {
34868
+ const caps = adapter.capabilities();
34869
+ if (!caps.videoGeneration || !adapter.submitVideo) {
34769
34870
  throw new Error(`Provider ${req.provider} does not support video generation`);
34770
34871
  }
34872
+ if (req.sourceVideo && !caps.videoExtension) {
34873
+ throw new Error(`Provider ${req.provider} does not support video extension/editing`);
34874
+ }
34771
34875
  const { trace, fetch: fetch2 } = this.tracedOp();
34772
34876
  const operationId = await adapter.submitVideo(req, fetch2);
34773
34877
  return this.pollVideoCompletion(adapter, operationId, req, fetch2, trace);
@@ -34800,7 +34904,8 @@ var MediaOutput = class {
34800
34904
  width: raw.width,
34801
34905
  height: raw.height,
34802
34906
  durationMs: raw.durationMs,
34803
- sampleRate: raw.sampleRate
34907
+ sampleRate: raw.sampleRate,
34908
+ sourceUrl: raw.sourceUrl
34804
34909
  };
34805
34910
  await this.mediaStore.save(id, raw.data, meta);
34806
34911
  results.push({ id, type, mimeType: raw.mimeType, meta });
@@ -34833,6 +34938,15 @@ var MediaOutput = class {
34833
34938
  const start = Date.now();
34834
34939
  while (Date.now() - start < this.maxPollWaitMs) {
34835
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
+ }
34836
34950
  if (status.status === "completed") {
34837
34951
  const raw = await adapter.downloadVideo(operationId, fetch2);
34838
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
@@ -26,6 +26,15 @@ export declare function toDataUrl(ref: NormalizedImageRef): string;
26
26
  export declare function openaiImageRef(ref: NormalizedImageRef): Record<string, string>;
27
27
  /** xAI image-ref object (`/v1/images/edits` image, video image). */
28
28
  export declare function xaiImageRef(ref: NormalizedImageRef): Record<string, string>;
29
+ /** xAI video-ref object (`/v1/videos/extensions` + `/v1/videos/edits` `video`
30
+ * field) — `{ url }` (public URL or base64 data-URL) or `{ file_id }`. Kept
31
+ * separate from the image path so it doesn't run image mime-sniffing over
32
+ * video bytes; the video mime is taken from the DataSource as declared. */
33
+ export declare function xaiVideoRef(src: DataSource): {
34
+ url: string;
35
+ } | {
36
+ file_id: string;
37
+ };
29
38
  /** Google generateContent image part (inline base64 or Files-API file_uri). */
30
39
  export declare function googleImagePart(ref: NormalizedImageRef): Record<string, unknown>;
31
40
  /** Google Veo instance image (`:predictLongRunning` instances[].image).