@ljoukov/llm 7.0.11 → 7.0.12

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/README.md CHANGED
@@ -628,20 +628,22 @@ Architecture note:
628
628
 
629
629
  ### Provider-Native Tools (`generateText()`)
630
630
 
631
- Use this when the model provider executes the tool remotely (for example search/code-exec style tools).
631
+ Use this when the model provider executes the tool remotely (for example search/code-exec/shell style tools).
632
632
 
633
633
  ```ts
634
634
  import { generateText } from "@ljoukov/llm";
635
635
 
636
636
  const result = await generateText({
637
- model: "gpt-5.4-mini",
638
- input: "Find 3 relevant sources about X and summarize them.",
639
- tools: [{ type: "web-search", mode: "live" }, { type: "code-execution" }],
637
+ model: "gpt-5.5",
638
+ input: "Run python --version in a hosted shell and summarize the result.",
639
+ tools: [{ type: "shell" }],
640
640
  });
641
641
 
642
642
  console.log(result.text);
643
643
  ```
644
644
 
645
+ `{ type: "shell" }` uses OpenAI hosted shell containers by default. It is only supported for OpenAI API models; ChatGPT-authenticated, Gemini, and Fireworks providers reject it.
646
+
645
647
  ### Runtime Tools (`runToolLoop()`)
646
648
 
647
649
  Use this when the model should call your local runtime functions.
package/dist/index.cjs CHANGED
@@ -6197,12 +6197,40 @@ function toGeminiTools(tools) {
6197
6197
  return { googleSearch: {} };
6198
6198
  case "code-execution":
6199
6199
  return { codeExecution: {} };
6200
+ case "shell":
6201
+ throw new Error("Gemini provider does not support the OpenAI shell tool.");
6200
6202
  default:
6201
6203
  throw new Error("Unsupported tool configuration");
6202
6204
  }
6203
6205
  });
6204
6206
  }
6205
- function toOpenAiTools(tools) {
6207
+ function toOpenAiShellEnvironment(environment) {
6208
+ if (environment?.type === "container-reference") {
6209
+ return {
6210
+ type: "container_reference",
6211
+ container_id: environment.containerId
6212
+ };
6213
+ }
6214
+ return {
6215
+ type: "container_auto",
6216
+ ...environment?.fileIds ? { file_ids: Array.from(environment.fileIds) } : {},
6217
+ ...environment?.memoryLimit !== void 0 ? { memory_limit: environment.memoryLimit } : {},
6218
+ ...environment?.networkPolicy ? {
6219
+ network_policy: environment.networkPolicy.type === "allowlist" ? {
6220
+ type: "allowlist",
6221
+ allowed_domains: Array.from(environment.networkPolicy.allowedDomains),
6222
+ ...environment.networkPolicy.domainSecrets ? {
6223
+ domain_secrets: environment.networkPolicy.domainSecrets.map((secret) => ({
6224
+ domain: secret.domain,
6225
+ name: secret.name,
6226
+ value: secret.value
6227
+ }))
6228
+ } : {}
6229
+ } : { type: "disabled" }
6230
+ } : {}
6231
+ };
6232
+ }
6233
+ function toOpenAiTools(tools, options) {
6206
6234
  if (!tools || tools.length === 0) {
6207
6235
  return void 0;
6208
6236
  }
@@ -6215,6 +6243,15 @@ function toOpenAiTools(tools) {
6215
6243
  case "code-execution": {
6216
6244
  return { type: "code_interpreter", container: { type: "auto" } };
6217
6245
  }
6246
+ case "shell": {
6247
+ if (options.provider !== "openai") {
6248
+ throw new Error("OpenAI shell tool is only supported for OpenAI API models.");
6249
+ }
6250
+ return {
6251
+ type: "shell",
6252
+ environment: toOpenAiShellEnvironment(tool2.environment)
6253
+ };
6254
+ }
6218
6255
  default:
6219
6256
  throw new Error("Unsupported tool configuration");
6220
6257
  }
@@ -7813,6 +7850,8 @@ async function runTextCall(params) {
7813
7850
  let responseRole;
7814
7851
  let latestUsage;
7815
7852
  let responseImages = 0;
7853
+ let sawResponseDelta = false;
7854
+ let sawThoughtDelta = false;
7816
7855
  const pushEvent = (event) => {
7817
7856
  queue.push(event);
7818
7857
  params.onEvent?.(event);
@@ -7823,8 +7862,10 @@ async function runTextCall(params) {
7823
7862
  }
7824
7863
  responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
7825
7864
  if (channel === "thought") {
7865
+ sawThoughtDelta = true;
7826
7866
  callLogger?.appendThoughtDelta(text);
7827
7867
  } else {
7868
+ sawResponseDelta = true;
7828
7869
  callLogger?.appendResponseDelta(text);
7829
7870
  }
7830
7871
  pushEvent({ type: "delta", channel, text });
@@ -7864,7 +7905,7 @@ async function runTextCall(params) {
7864
7905
  }),
7865
7906
  { model: request.model, provider: "openai" }
7866
7907
  );
7867
- const openAiTools = toOpenAiTools(request.tools);
7908
+ const openAiTools = toOpenAiTools(request.tools, { provider: "openai" });
7868
7909
  const reasoningEffort = resolveOpenAiReasoningEffort(
7869
7910
  modelForProvider,
7870
7911
  request.thinkingLevel
@@ -7925,12 +7966,17 @@ async function runTextCall(params) {
7925
7966
  );
7926
7967
  }
7927
7968
  latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
7928
- if (responseParts.length === 0) {
7969
+ if (!sawResponseDelta || !sawThoughtDelta) {
7970
+ const needsResponseFallback = !sawResponseDelta;
7971
+ const needsThoughtFallback = !sawThoughtDelta;
7929
7972
  const fallback = extractOpenAiResponseParts(finalResponse);
7930
7973
  blocked = blocked || fallback.blocked;
7931
7974
  for (const part of fallback.parts) {
7932
7975
  if (part.type === "text") {
7933
- pushDelta(part.thought === true ? "thought" : "response", part.text);
7976
+ const channel = part.thought === true ? "thought" : "response";
7977
+ if (channel === "response" && needsResponseFallback || channel === "thought" && needsThoughtFallback) {
7978
+ pushDelta(channel, part.text);
7979
+ }
7934
7980
  } else if (part.type === "inlineData") {
7935
7981
  pushInline(part.data, part.mimeType);
7936
7982
  }
@@ -7947,7 +7993,7 @@ async function runTextCall(params) {
7947
7993
  provider: "chatgpt"
7948
7994
  });
7949
7995
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
7950
- const openAiTools = toOpenAiTools(request.tools);
7996
+ const openAiTools = toOpenAiTools(request.tools, { provider: "chatgpt" });
7951
7997
  const requestPayload = {
7952
7998
  model: modelForProvider,
7953
7999
  store: false,
@@ -7966,18 +8012,18 @@ async function runTextCall(params) {
7966
8012
  },
7967
8013
  ...openAiTools ? { tools: openAiTools } : {}
7968
8014
  };
7969
- let sawResponseDelta = false;
7970
- let sawThoughtDelta = false;
8015
+ let sawResponseDelta2 = false;
8016
+ let sawThoughtDelta2 = false;
7971
8017
  const result2 = await collectChatGptCodexResponseWithRetry({
7972
8018
  request: requestPayload,
7973
8019
  signal,
7974
8020
  onDelta: (delta) => {
7975
8021
  if (delta.thoughtDelta) {
7976
- sawThoughtDelta = true;
8022
+ sawThoughtDelta2 = true;
7977
8023
  pushDelta("thought", delta.thoughtDelta);
7978
8024
  }
7979
8025
  if (delta.textDelta) {
7980
- sawResponseDelta = true;
8026
+ sawResponseDelta2 = true;
7981
8027
  pushDelta("response", delta.textDelta);
7982
8028
  }
7983
8029
  }
@@ -7993,10 +8039,10 @@ async function runTextCall(params) {
7993
8039
  latestUsage = extractChatGptUsageTokens(result2.usage);
7994
8040
  const fallbackText = typeof result2.text === "string" ? result2.text : "";
7995
8041
  const fallbackThoughts = typeof result2.reasoningSummaryText === "string" && result2.reasoningSummaryText.length > 0 ? result2.reasoningSummaryText : typeof result2.reasoningText === "string" ? result2.reasoningText : "";
7996
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
8042
+ if (!sawThoughtDelta2 && fallbackThoughts.length > 0) {
7997
8043
  pushDelta("thought", fallbackThoughts);
7998
8044
  }
7999
- if (!sawResponseDelta && fallbackText.length > 0) {
8045
+ if (!sawResponseDelta2 && fallbackText.length > 0) {
8000
8046
  pushDelta("response", fallbackText);
8001
8047
  }
8002
8048
  } else if (provider === "fireworks") {
@@ -8716,7 +8762,7 @@ async function runToolLoop(request) {
8716
8762
  try {
8717
8763
  if (providerInfo.provider === "openai") {
8718
8764
  const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
8719
- const openAiNativeTools = toOpenAiTools(request.modelTools);
8765
+ const openAiNativeTools = toOpenAiTools(request.modelTools, { provider: "openai" });
8720
8766
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
8721
8767
  const reasoningEffort = resolveOpenAiReasoningEffort(
8722
8768
  providerInfo.model,
@@ -9119,7 +9165,7 @@ async function runToolLoop(request) {
9119
9165
  }
9120
9166
  if (providerInfo.provider === "chatgpt") {
9121
9167
  const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
9122
- const openAiNativeTools = toOpenAiTools(request.modelTools);
9168
+ const openAiNativeTools = toOpenAiTools(request.modelTools, { provider: "chatgpt" });
9123
9169
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
9124
9170
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
9125
9171
  const toolLoopInput = toChatGptInput(contents, {