@ljoukov/llm 7.0.10 → 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
@@ -450,19 +450,19 @@ Use a `chatgpt-` prefix:
450
450
  import { generateText } from "@ljoukov/llm";
451
451
 
452
452
  const result = await generateText({
453
- model: "chatgpt-gpt-5.4",
453
+ model: "chatgpt-gpt-5.5",
454
454
  input: "Return exactly: OK",
455
455
  });
456
456
 
457
457
  console.log(result.text);
458
458
  ```
459
459
 
460
- `chatgpt-gpt-5.4-fast` is also supported as a convenience alias for ChatGPT-authenticated `gpt-5.4` with priority processing enabled (`service_tier="priority"`), matching Codex `/fast` semantics.
460
+ `gpt-5.5-fast` and `chatgpt-gpt-5.5-fast` are also supported as convenience aliases for `gpt-5.5` with priority processing enabled (`service_tier="priority"`), matching Codex `/fast` semantics.
461
461
 
462
462
  Supported OpenAI text model ids are fixed literal unions in code, not arbitrary strings:
463
463
 
464
- - OpenAI API: `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`
465
- - ChatGPT auth: `chatgpt-gpt-5.4`, `chatgpt-gpt-5.4-fast`, `chatgpt-gpt-5.4-mini`, `chatgpt-gpt-5.3-codex-spark`
464
+ - OpenAI API: `gpt-5.5`, `gpt-5.5-fast`, `gpt-5.4`, `gpt-5.4-mini`, `gpt-5.4-nano`
465
+ - ChatGPT auth: `chatgpt-gpt-5.5`, `chatgpt-gpt-5.5-fast`, `chatgpt-gpt-5.4`, `chatgpt-gpt-5.4-fast`, `chatgpt-gpt-5.4-mini`, `chatgpt-gpt-5.3-codex-spark`
466
466
 
467
467
  ## JSON outputs
468
468
 
@@ -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.
@@ -742,7 +744,7 @@ console.log(result.text);
742
744
  For read/search/write tasks in a workspace, enable `filesystemTool`. The library auto-selects a tool profile by model
743
745
  when `profile: "auto"`:
744
746
 
745
- - Codex-like models (`gpt-5.4`, `chatgpt-gpt-5.4`, `chatgpt-gpt-5.4-fast`, and `chatgpt-gpt-5.3-codex-spark`): Codex-compatible filesystem tool shape.
747
+ - Codex-like models (`gpt-5.5`, `gpt-5.5-fast`, `chatgpt-gpt-5.5`, `chatgpt-gpt-5.5-fast`, `gpt-5.4`, `chatgpt-gpt-5.4`, `chatgpt-gpt-5.4-fast`, and `chatgpt-gpt-5.3-codex-spark`): Codex-compatible filesystem tool shape.
746
748
  - Gemini models: Gemini-compatible filesystem tool shape.
747
749
  - Other models: model-agnostic profile (currently Gemini-style).
748
750
 
package/dist/index.cjs CHANGED
@@ -321,16 +321,27 @@ function getGeminiImagePricing(modelId) {
321
321
  }
322
322
 
323
323
  // src/openai/models.ts
324
- var OPENAI_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini", "gpt-5.4-nano"];
324
+ var OPENAI_MODEL_IDS = [
325
+ "gpt-5.5",
326
+ "gpt-5.5-fast",
327
+ "gpt-5.4",
328
+ "gpt-5.4-mini",
329
+ "gpt-5.4-nano"
330
+ ];
325
331
  function isOpenAiModelId(value) {
326
332
  return OPENAI_MODEL_IDS.includes(value);
327
333
  }
328
334
  var CHATGPT_MODEL_IDS = [
335
+ "chatgpt-gpt-5.5",
336
+ "chatgpt-gpt-5.5-fast",
329
337
  "chatgpt-gpt-5.4",
330
338
  "chatgpt-gpt-5.4-fast",
331
339
  "chatgpt-gpt-5.4-mini",
332
340
  "chatgpt-gpt-5.3-codex-spark"
333
341
  ];
342
+ var FAST_MODEL_SUFFIX = "-fast";
343
+ var OPENAI_PRIORITY_MODEL_IDS = ["gpt-5.5-fast"];
344
+ var CHATGPT_PRIORITY_MODEL_IDS = ["chatgpt-gpt-5.5-fast", "chatgpt-gpt-5.4-fast"];
334
345
  var EXPERIMENTAL_CHATGPT_MODEL_PREFIX = "experimental-chatgpt-";
335
346
  function isExperimentalChatGptModelId(value) {
336
347
  return value.startsWith(EXPERIMENTAL_CHATGPT_MODEL_PREFIX) && value.length > EXPERIMENTAL_CHATGPT_MODEL_PREFIX.length;
@@ -338,6 +349,15 @@ function isExperimentalChatGptModelId(value) {
338
349
  function isChatGptModelId(value) {
339
350
  return CHATGPT_MODEL_IDS.includes(value) || isExperimentalChatGptModelId(value);
340
351
  }
352
+ function stripFastSuffix(model) {
353
+ return model.endsWith(FAST_MODEL_SUFFIX) ? model.slice(0, -FAST_MODEL_SUFFIX.length) : model;
354
+ }
355
+ function resolveOpenAiProviderModel(model) {
356
+ return OPENAI_PRIORITY_MODEL_IDS.includes(model) ? stripFastSuffix(model) : model;
357
+ }
358
+ function resolveOpenAiServiceTier(model) {
359
+ return OPENAI_PRIORITY_MODEL_IDS.includes(model) ? "priority" : void 0;
360
+ }
341
361
  function stripChatGptPrefix(model) {
342
362
  if (isExperimentalChatGptModelId(model)) {
343
363
  return model.slice(EXPERIMENTAL_CHATGPT_MODEL_PREFIX.length);
@@ -345,18 +365,16 @@ function stripChatGptPrefix(model) {
345
365
  return model.slice("chatgpt-".length);
346
366
  }
347
367
  function resolveChatGptProviderModel(model) {
348
- switch (model) {
349
- case "chatgpt-gpt-5.4-fast":
350
- return "gpt-5.4";
351
- default:
352
- return stripChatGptPrefix(model);
353
- }
368
+ const providerModel = stripChatGptPrefix(model);
369
+ return CHATGPT_PRIORITY_MODEL_IDS.includes(model) ? stripFastSuffix(providerModel) : providerModel;
354
370
  }
355
371
  function resolveChatGptServiceTier(model) {
356
- return model === "chatgpt-gpt-5.4-fast" ? "priority" : void 0;
372
+ return CHATGPT_PRIORITY_MODEL_IDS.includes(model) ? "priority" : void 0;
357
373
  }
358
374
 
359
375
  // src/openai/pricing.ts
376
+ var OPENAI_GPT_55_FAST_MODEL_IDS = ["gpt-5.5-fast", "chatgpt-gpt-5.5-fast"];
377
+ var OPENAI_GPT_55_STANDARD_MODEL_IDS = ["gpt-5.5", "chatgpt-gpt-5.5"];
360
378
  var OPENAI_GPT_54_FAST_MODEL_IDS = ["gpt-5.4-fast", "chatgpt-gpt-5.4-fast"];
361
379
  var OPENAI_GPT_54_MINI_MODEL_IDS = ["gpt-5.4-mini", "chatgpt-gpt-5.4-mini"];
362
380
  var OPENAI_GPT_54_NANO_MODEL_IDS = ["gpt-5.4-nano"];
@@ -365,6 +383,16 @@ var OPENAI_GPT_53_CODEX_SPARK_MODEL_IDS = [
365
383
  "chatgpt-gpt-5.3-codex-spark"
366
384
  ];
367
385
  var OPENAI_GPT_54_STANDARD_MODEL_IDS = ["gpt-5.4", "chatgpt-gpt-5.4"];
386
+ var OPENAI_GPT_55_PRICING = {
387
+ inputRate: 5 / 1e6,
388
+ cachedRate: 0.5 / 1e6,
389
+ outputRate: 30 / 1e6
390
+ };
391
+ var OPENAI_GPT_55_PRIORITY_PRICING = {
392
+ inputRate: 12.5 / 1e6,
393
+ cachedRate: 1.25 / 1e6,
394
+ outputRate: 75 / 1e6
395
+ };
368
396
  var OPENAI_GPT_54_PRICING = {
369
397
  inputRate: 2.5 / 1e6,
370
398
  cachedRate: 0.25 / 1e6,
@@ -389,6 +417,12 @@ function getOpenAiPricing(modelId) {
389
417
  if (isExperimentalChatGptModelId(modelId)) {
390
418
  return OPENAI_GPT_54_PRICING;
391
419
  }
420
+ if (OPENAI_GPT_55_FAST_MODEL_IDS.includes(modelId)) {
421
+ return OPENAI_GPT_55_PRIORITY_PRICING;
422
+ }
423
+ if (OPENAI_GPT_55_STANDARD_MODEL_IDS.includes(modelId)) {
424
+ return OPENAI_GPT_55_PRICING;
425
+ }
392
426
  if (OPENAI_GPT_54_FAST_MODEL_IDS.includes(modelId)) {
393
427
  return OPENAI_GPT_54_PRIORITY_PRICING;
394
428
  }
@@ -5070,7 +5104,11 @@ function resolveProvider(model) {
5070
5104
  }
5071
5105
  }
5072
5106
  if (isOpenAiModelId(model)) {
5073
- return { provider: "openai", model };
5107
+ return {
5108
+ provider: "openai",
5109
+ model: resolveOpenAiProviderModel(model),
5110
+ serviceTier: resolveOpenAiServiceTier(model)
5111
+ };
5074
5112
  }
5075
5113
  throw new Error(`Unsupported text model: ${model}`);
5076
5114
  }
@@ -6159,12 +6197,40 @@ function toGeminiTools(tools) {
6159
6197
  return { googleSearch: {} };
6160
6198
  case "code-execution":
6161
6199
  return { codeExecution: {} };
6200
+ case "shell":
6201
+ throw new Error("Gemini provider does not support the OpenAI shell tool.");
6162
6202
  default:
6163
6203
  throw new Error("Unsupported tool configuration");
6164
6204
  }
6165
6205
  });
6166
6206
  }
6167
- 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) {
6168
6234
  if (!tools || tools.length === 0) {
6169
6235
  return void 0;
6170
6236
  }
@@ -6177,6 +6243,15 @@ function toOpenAiTools(tools) {
6177
6243
  case "code-execution": {
6178
6244
  return { type: "code_interpreter", container: { type: "auto" } };
6179
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
+ }
6180
6255
  default:
6181
6256
  throw new Error("Unsupported tool configuration");
6182
6257
  }
@@ -7775,6 +7850,8 @@ async function runTextCall(params) {
7775
7850
  let responseRole;
7776
7851
  let latestUsage;
7777
7852
  let responseImages = 0;
7853
+ let sawResponseDelta = false;
7854
+ let sawThoughtDelta = false;
7778
7855
  const pushEvent = (event) => {
7779
7856
  queue.push(event);
7780
7857
  params.onEvent?.(event);
@@ -7785,8 +7862,10 @@ async function runTextCall(params) {
7785
7862
  }
7786
7863
  responseParts.push({ type: "text", text, ...channel === "thought" ? { thought: true } : {} });
7787
7864
  if (channel === "thought") {
7865
+ sawThoughtDelta = true;
7788
7866
  callLogger?.appendThoughtDelta(text);
7789
7867
  } else {
7868
+ sawResponseDelta = true;
7790
7869
  callLogger?.appendResponseDelta(text);
7791
7870
  }
7792
7871
  pushEvent({ type: "delta", channel, text });
@@ -7826,7 +7905,7 @@ async function runTextCall(params) {
7826
7905
  }),
7827
7906
  { model: request.model, provider: "openai" }
7828
7907
  );
7829
- const openAiTools = toOpenAiTools(request.tools);
7908
+ const openAiTools = toOpenAiTools(request.tools, { provider: "openai" });
7830
7909
  const reasoningEffort = resolveOpenAiReasoningEffort(
7831
7910
  modelForProvider,
7832
7911
  request.thinkingLevel
@@ -7844,6 +7923,7 @@ async function runTextCall(params) {
7844
7923
  {
7845
7924
  model: modelForProvider,
7846
7925
  input: openAiInput,
7926
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
7847
7927
  reasoning,
7848
7928
  text: openAiTextConfig,
7849
7929
  ...openAiTools ? { tools: openAiTools } : {},
@@ -7886,12 +7966,17 @@ async function runTextCall(params) {
7886
7966
  );
7887
7967
  }
7888
7968
  latestUsage = extractOpenAiUsageTokens(finalResponse.usage);
7889
- if (responseParts.length === 0) {
7969
+ if (!sawResponseDelta || !sawThoughtDelta) {
7970
+ const needsResponseFallback = !sawResponseDelta;
7971
+ const needsThoughtFallback = !sawThoughtDelta;
7890
7972
  const fallback = extractOpenAiResponseParts(finalResponse);
7891
7973
  blocked = blocked || fallback.blocked;
7892
7974
  for (const part of fallback.parts) {
7893
7975
  if (part.type === "text") {
7894
- 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
+ }
7895
7980
  } else if (part.type === "inlineData") {
7896
7981
  pushInline(part.data, part.mimeType);
7897
7982
  }
@@ -7908,7 +7993,7 @@ async function runTextCall(params) {
7908
7993
  provider: "chatgpt"
7909
7994
  });
7910
7995
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
7911
- const openAiTools = toOpenAiTools(request.tools);
7996
+ const openAiTools = toOpenAiTools(request.tools, { provider: "chatgpt" });
7912
7997
  const requestPayload = {
7913
7998
  model: modelForProvider,
7914
7999
  store: false,
@@ -7927,18 +8012,18 @@ async function runTextCall(params) {
7927
8012
  },
7928
8013
  ...openAiTools ? { tools: openAiTools } : {}
7929
8014
  };
7930
- let sawResponseDelta = false;
7931
- let sawThoughtDelta = false;
8015
+ let sawResponseDelta2 = false;
8016
+ let sawThoughtDelta2 = false;
7932
8017
  const result2 = await collectChatGptCodexResponseWithRetry({
7933
8018
  request: requestPayload,
7934
8019
  signal,
7935
8020
  onDelta: (delta) => {
7936
8021
  if (delta.thoughtDelta) {
7937
- sawThoughtDelta = true;
8022
+ sawThoughtDelta2 = true;
7938
8023
  pushDelta("thought", delta.thoughtDelta);
7939
8024
  }
7940
8025
  if (delta.textDelta) {
7941
- sawResponseDelta = true;
8026
+ sawResponseDelta2 = true;
7942
8027
  pushDelta("response", delta.textDelta);
7943
8028
  }
7944
8029
  }
@@ -7954,10 +8039,10 @@ async function runTextCall(params) {
7954
8039
  latestUsage = extractChatGptUsageTokens(result2.usage);
7955
8040
  const fallbackText = typeof result2.text === "string" ? result2.text : "";
7956
8041
  const fallbackThoughts = typeof result2.reasoningSummaryText === "string" && result2.reasoningSummaryText.length > 0 ? result2.reasoningSummaryText : typeof result2.reasoningText === "string" ? result2.reasoningText : "";
7957
- if (!sawThoughtDelta && fallbackThoughts.length > 0) {
8042
+ if (!sawThoughtDelta2 && fallbackThoughts.length > 0) {
7958
8043
  pushDelta("thought", fallbackThoughts);
7959
8044
  }
7960
- if (!sawResponseDelta && fallbackText.length > 0) {
8045
+ if (!sawResponseDelta2 && fallbackText.length > 0) {
7961
8046
  pushDelta("response", fallbackText);
7962
8047
  }
7963
8048
  } else if (provider === "fireworks") {
@@ -8677,7 +8762,7 @@ async function runToolLoop(request) {
8677
8762
  try {
8678
8763
  if (providerInfo.provider === "openai") {
8679
8764
  const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
8680
- const openAiNativeTools = toOpenAiTools(request.modelTools);
8765
+ const openAiNativeTools = toOpenAiTools(request.modelTools, { provider: "openai" });
8681
8766
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
8682
8767
  const reasoningEffort = resolveOpenAiReasoningEffort(
8683
8768
  providerInfo.model,
@@ -8729,6 +8814,7 @@ async function runToolLoop(request) {
8729
8814
  const stepRequestPayload = {
8730
8815
  model: providerInfo.model,
8731
8816
  input: preparedInput,
8817
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
8732
8818
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
8733
8819
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
8734
8820
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -8757,6 +8843,7 @@ async function runToolLoop(request) {
8757
8843
  {
8758
8844
  model: providerInfo.model,
8759
8845
  input: preparedInput,
8846
+ ...providerInfo.serviceTier ? { service_tier: providerInfo.serviceTier } : {},
8760
8847
  ...previousResponseId ? { previous_response_id: previousResponseId } : {},
8761
8848
  ...openAiTools.length > 0 ? { tools: openAiTools } : {},
8762
8849
  ...openAiTools.length > 0 ? { parallel_tool_calls: true } : {},
@@ -9078,7 +9165,7 @@ async function runToolLoop(request) {
9078
9165
  }
9079
9166
  if (providerInfo.provider === "chatgpt") {
9080
9167
  const openAiAgentTools = buildOpenAiToolsFromToolSet(request.tools);
9081
- const openAiNativeTools = toOpenAiTools(request.modelTools);
9168
+ const openAiNativeTools = toOpenAiTools(request.modelTools, { provider: "chatgpt" });
9082
9169
  const openAiTools = openAiNativeTools ? [...openAiNativeTools, ...openAiAgentTools] : [...openAiAgentTools];
9083
9170
  const reasoningEffort = resolveOpenAiReasoningEffort(request.model, request.thinkingLevel);
9084
9171
  const toolLoopInput = toChatGptInput(contents, {
@@ -12768,7 +12855,7 @@ function isCodexModel(model) {
12768
12855
  return true;
12769
12856
  }
12770
12857
  const normalized = model.startsWith("chatgpt-") ? model.slice("chatgpt-".length) : model;
12771
- return normalized.includes("codex") || normalized === "gpt-5.4" || normalized === "gpt-5.4-fast";
12858
+ return normalized.includes("codex") || normalized === "gpt-5.5" || normalized === "gpt-5.5-fast" || normalized === "gpt-5.4" || normalized === "gpt-5.4-fast";
12772
12859
  }
12773
12860
  function isGeminiModel(model) {
12774
12861
  return model.startsWith("gemini-");