@ljoukov/llm 7.0.14 → 7.0.16

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
@@ -699,6 +699,57 @@ console.log(result.text);
699
699
 
700
700
  `{ 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.
701
701
 
702
+ When the shell writes artifacts under `/mnt/data`, `generateText()` returns the
703
+ OpenAI container reference and the library can retrieve the resulting files:
704
+
705
+ ```ts
706
+ import { downloadOpenAiContainerFile, generateText, listOpenAiContainerFiles } from "@ljoukov/llm";
707
+
708
+ const result = await generateText({
709
+ model: "gpt-5.5",
710
+ input:
711
+ "Use the shell to write /mnt/data/report.txt containing 'hello'. Reply only when done.",
712
+ tools: [{ type: "shell" }],
713
+ });
714
+
715
+ const containerId = result.openAi?.containers.find((c) => c.toolType === "shell")?.containerId;
716
+ if (!containerId) throw new Error("The response did not include a hosted shell container.");
717
+
718
+ const files = await listOpenAiContainerFiles(containerId);
719
+ const report = files.find((file) => file.path === "/mnt/data/report.txt");
720
+ if (!report) throw new Error("The shell did not create report.txt.");
721
+
722
+ const bytes = await downloadOpenAiContainerFile({
723
+ containerId,
724
+ fileId: report.id,
725
+ });
726
+ ```
727
+
728
+ For persistent multi-step shell work, create a container first, upload any input
729
+ assets, then pass it back as a `container-reference`:
730
+
731
+ ```ts
732
+ import {
733
+ createOpenAiContainer,
734
+ generateText,
735
+ uploadOpenAiContainerFile,
736
+ } from "@ljoukov/llm";
737
+
738
+ const container = await createOpenAiContainer({ name: "latex-build", memoryLimit: "1g" });
739
+ const cover = await uploadOpenAiContainerFile({
740
+ containerId: container.id,
741
+ filename: "cover.png",
742
+ data: coverPngBytes,
743
+ mimeType: "image/png",
744
+ });
745
+
746
+ await generateText({
747
+ model: "gpt-5.5",
748
+ input: `Use ${cover.path}, write LaTeX, run xelatex, and save /mnt/data/article.pdf.`,
749
+ tools: [{ type: "shell", environment: { type: "container-reference", containerId: container.id } }],
750
+ });
751
+ ```
752
+
702
753
  ### Runtime Tools (`runToolLoop()`)
703
754
 
704
755
  Use this when the model should call your local runtime functions.
package/dist/index.cjs CHANGED
@@ -84,12 +84,16 @@ __export(index_exports, {
84
84
  createListDirectoryTool: () => createListDirectoryTool,
85
85
  createModelAgnosticFilesystemToolSet: () => createModelAgnosticFilesystemToolSet,
86
86
  createNodeAgentFilesystem: () => createNodeAgentFilesystem,
87
+ createOpenAiContainer: () => createOpenAiContainer,
87
88
  createReplaceTool: () => createReplaceTool,
88
89
  createRgSearchTool: () => createRgSearchTool,
89
90
  createToolLoopSteeringChannel: () => createToolLoopSteeringChannel,
90
91
  createViewImageTool: () => createViewImageTool,
91
92
  createWriteFileTool: () => createWriteFileTool,
92
93
  customTool: () => customTool,
94
+ deleteOpenAiContainer: () => deleteOpenAiContainer,
95
+ downloadOpenAiContainerFile: () => downloadOpenAiContainerFile,
96
+ downloadOpenAiContainerFileText: () => downloadOpenAiContainerFileText,
93
97
  emptyFileUploadMetrics: () => emptyFileUploadMetrics,
94
98
  encodeChatGptAuthJson: () => encodeChatGptAuthJson,
95
99
  encodeChatGptAuthJsonB64: () => encodeChatGptAuthJsonB64,
@@ -114,6 +118,7 @@ __export(index_exports, {
114
118
  isLlmTextModelId: () => isLlmTextModelId,
115
119
  isOpenAiImageModelId: () => isOpenAiImageModelId,
116
120
  isOpenAiModelId: () => isOpenAiModelId,
121
+ listOpenAiContainerFiles: () => listOpenAiContainerFiles,
117
122
  loadEnvFromFile: () => loadEnvFromFile,
118
123
  loadLocalEnv: () => loadLocalEnv,
119
124
  parseJsonFromLlmText: () => parseJsonFromLlmText,
@@ -134,6 +139,7 @@ __export(index_exports, {
134
139
  stripCodexCitationMarkers: () => stripCodexCitationMarkers,
135
140
  toGeminiJsonSchema: () => toGeminiJsonSchema,
136
141
  tool: () => tool,
142
+ uploadOpenAiContainerFile: () => uploadOpenAiContainerFile,
137
143
  validateOpenAiGptImage2Resolution: () => validateOpenAiGptImage2Resolution
138
144
  });
139
145
  module.exports = __toCommonJS(index_exports);
@@ -5328,6 +5334,8 @@ function resolveOpenAiReasoningEffort(modelId, thinkingLevel) {
5328
5334
  case "medium":
5329
5335
  return "medium";
5330
5336
  case "high":
5337
+ return "high";
5338
+ case "xhigh":
5331
5339
  return "xhigh";
5332
5340
  }
5333
5341
  }
@@ -5345,7 +5353,7 @@ function toOpenAiReasoningEffort(effort) {
5345
5353
  case "high":
5346
5354
  return "high";
5347
5355
  case "xhigh":
5348
- return "high";
5356
+ return "xhigh";
5349
5357
  }
5350
5358
  }
5351
5359
  function resolveOpenAiVerbosity(modelId) {
@@ -6462,6 +6470,50 @@ function toOpenAiTools(tools, options) {
6462
6470
  }
6463
6471
  });
6464
6472
  }
6473
+ function extractOpenAiResponseMetadata(response) {
6474
+ if (!response || typeof response !== "object") {
6475
+ return void 0;
6476
+ }
6477
+ const record = response;
6478
+ const responseId = typeof record.id === "string" ? record.id : void 0;
6479
+ const output = Array.isArray(record.output) ? record.output : [];
6480
+ const containers = [];
6481
+ const seen = /* @__PURE__ */ new Set();
6482
+ const addContainer = (container) => {
6483
+ const key = `${container.toolType}:${container.containerId}:${container.itemId ?? ""}:${container.callId ?? ""}`;
6484
+ if (seen.has(key)) {
6485
+ return;
6486
+ }
6487
+ seen.add(key);
6488
+ containers.push(container);
6489
+ };
6490
+ for (const item of output) {
6491
+ if (!item || typeof item !== "object") {
6492
+ continue;
6493
+ }
6494
+ const itemRecord = item;
6495
+ const itemId = typeof itemRecord.id === "string" ? itemRecord.id : void 0;
6496
+ const callId = typeof itemRecord.call_id === "string" ? itemRecord.call_id : void 0;
6497
+ if (itemRecord.type === "shell_call") {
6498
+ const environment = itemRecord.environment && typeof itemRecord.environment === "object" ? itemRecord.environment : void 0;
6499
+ const containerId = environment?.type === "container_reference" && typeof environment.container_id === "string" ? environment.container_id : void 0;
6500
+ if (containerId) {
6501
+ addContainer({ containerId, toolType: "shell", itemId, callId });
6502
+ }
6503
+ continue;
6504
+ }
6505
+ if (itemRecord.type === "code_interpreter_call") {
6506
+ const containerId = typeof itemRecord.container_id === "string" ? itemRecord.container_id : void 0;
6507
+ if (containerId) {
6508
+ addContainer({ containerId, toolType: "code_interpreter", itemId, callId });
6509
+ }
6510
+ }
6511
+ }
6512
+ if (!responseId && containers.length === 0) {
6513
+ return void 0;
6514
+ }
6515
+ return { ...responseId ? { responseId } : {}, containers };
6516
+ }
6465
6517
  function mergeTokenUpdates(current, next) {
6466
6518
  if (!next) {
6467
6519
  return current;
@@ -7594,6 +7646,7 @@ function toGeminiThinkingLevel(thinkingLevel) {
7594
7646
  case "medium":
7595
7647
  return import_genai2.ThinkingLevel.MEDIUM;
7596
7648
  case "high":
7649
+ case "xhigh":
7597
7650
  return import_genai2.ThinkingLevel.HIGH;
7598
7651
  }
7599
7652
  }
@@ -7636,6 +7689,7 @@ function resolveGeminiThinkingBudget(modelId, thinkingLevel) {
7636
7689
  case "medium":
7637
7690
  return 4096;
7638
7691
  case "high":
7692
+ case "xhigh":
7639
7693
  return 32768;
7640
7694
  }
7641
7695
  }
@@ -7646,6 +7700,7 @@ function resolveGeminiThinkingBudget(modelId, thinkingLevel) {
7646
7700
  case "medium":
7647
7701
  return 8192;
7648
7702
  case "high":
7703
+ case "xhigh":
7649
7704
  return 24576;
7650
7705
  }
7651
7706
  }
@@ -7656,6 +7711,7 @@ function resolveGeminiThinkingBudget(modelId, thinkingLevel) {
7656
7711
  case "medium":
7657
7712
  return 8192;
7658
7713
  case "high":
7714
+ case "xhigh":
7659
7715
  return 16384;
7660
7716
  }
7661
7717
  }
@@ -8073,6 +8129,7 @@ async function runTextCall(params) {
8073
8129
  let modelVersion = request.model;
8074
8130
  let blocked = false;
8075
8131
  let grounding;
8132
+ let openAi;
8076
8133
  const responseParts = [];
8077
8134
  let responseRole;
8078
8135
  let latestUsage;
@@ -8183,6 +8240,7 @@ async function runTextCall(params) {
8183
8240
  }
8184
8241
  }
8185
8242
  const finalResponse = await stream.finalResponse();
8243
+ openAi = extractOpenAiResponseMetadata(finalResponse);
8186
8244
  modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
8187
8245
  pushEvent({ type: "model", modelVersion });
8188
8246
  if (finalResponse.error) {
@@ -8426,6 +8484,7 @@ async function runTextCall(params) {
8426
8484
  costUsd,
8427
8485
  usage: latestUsage,
8428
8486
  grounding: grounding ? sanitiseLogValue(grounding) : void 0,
8487
+ openAi: openAi ? sanitiseLogValue(openAi) : void 0,
8429
8488
  responseChars: text.length,
8430
8489
  thoughtChars: thoughts.length,
8431
8490
  responseImages,
@@ -8442,7 +8501,8 @@ async function runTextCall(params) {
8442
8501
  blocked,
8443
8502
  usage: latestUsage,
8444
8503
  costUsd,
8445
- grounding
8504
+ grounding,
8505
+ openAi
8446
8506
  };
8447
8507
  } catch (error) {
8448
8508
  const partialParts = mergeConsecutiveTextParts(responseParts);
@@ -11091,6 +11151,104 @@ function appendMarkdownSourcesSection(value, sources) {
11091
11151
  ${lines}`;
11092
11152
  }
11093
11153
 
11154
+ // src/openai/containers.ts
11155
+ var import_openai4 = require("openai");
11156
+ function toOpenAiContainerNetworkPolicy(policy) {
11157
+ if (!policy) {
11158
+ return void 0;
11159
+ }
11160
+ if (policy.type === "disabled") {
11161
+ return { type: "disabled" };
11162
+ }
11163
+ return {
11164
+ type: "allowlist",
11165
+ allowed_domains: Array.from(policy.allowedDomains),
11166
+ ...policy.domainSecrets ? {
11167
+ domain_secrets: policy.domainSecrets.map((secret) => ({
11168
+ domain: secret.domain,
11169
+ name: secret.name,
11170
+ value: secret.value
11171
+ }))
11172
+ } : {}
11173
+ };
11174
+ }
11175
+ function toContainer(container) {
11176
+ return {
11177
+ id: String(container.id),
11178
+ name: typeof container.name === "string" ? container.name : "",
11179
+ status: typeof container.status === "string" ? container.status : "",
11180
+ ...typeof container.created_at === "number" ? { createdAt: container.created_at } : {},
11181
+ ...typeof container.last_active_at === "number" ? { lastActiveAt: container.last_active_at } : {},
11182
+ ...typeof container.memory_limit === "string" ? { memoryLimit: container.memory_limit } : {}
11183
+ };
11184
+ }
11185
+ function toContainerFile(file) {
11186
+ return {
11187
+ id: String(file.id),
11188
+ containerId: typeof file.container_id === "string" ? file.container_id : "",
11189
+ path: typeof file.path === "string" ? file.path : "",
11190
+ ...typeof file.bytes === "number" || file.bytes === null ? { bytes: file.bytes } : {},
11191
+ ...typeof file.created_at === "number" ? { createdAt: file.created_at } : {},
11192
+ ...typeof file.source === "string" ? { source: file.source } : {}
11193
+ };
11194
+ }
11195
+ async function createOpenAiContainer(options) {
11196
+ const container = await runOpenAiCall(
11197
+ async (client) => await client.containers.create({
11198
+ name: options.name,
11199
+ ...options.fileIds ? { file_ids: Array.from(options.fileIds) } : {},
11200
+ ...options.memoryLimit ? { memory_limit: options.memoryLimit } : {},
11201
+ ...options.networkPolicy ? { network_policy: toOpenAiContainerNetworkPolicy(options.networkPolicy) } : {},
11202
+ ...options.expiresAfterMinutes ? {
11203
+ expires_after: {
11204
+ anchor: "last_active_at",
11205
+ minutes: options.expiresAfterMinutes
11206
+ }
11207
+ } : {}
11208
+ }),
11209
+ "openai-containers"
11210
+ );
11211
+ return toContainer(container);
11212
+ }
11213
+ async function deleteOpenAiContainer(containerId) {
11214
+ await runOpenAiCall(
11215
+ async (client) => await client.containers.delete(containerId),
11216
+ "openai-containers"
11217
+ );
11218
+ }
11219
+ async function listOpenAiContainerFiles(containerId) {
11220
+ const files2 = [];
11221
+ await runOpenAiCall(async (client) => {
11222
+ for await (const file of client.containers.files.list(containerId)) {
11223
+ files2.push(toContainerFile(file));
11224
+ }
11225
+ }, "openai-containers");
11226
+ return files2;
11227
+ }
11228
+ async function uploadOpenAiContainerFile(upload) {
11229
+ const file = await (0, import_openai4.toFile)(upload.data, upload.filename, {
11230
+ ...upload.mimeType ? { type: upload.mimeType } : {}
11231
+ });
11232
+ const created = await runOpenAiCall(
11233
+ async (client) => await client.containers.files.create(upload.containerId, { file }),
11234
+ "openai-containers"
11235
+ );
11236
+ return toContainerFile(created);
11237
+ }
11238
+ async function downloadOpenAiContainerFile(params) {
11239
+ const response = await runOpenAiCall(
11240
+ async (client) => await client.containers.files.content.retrieve(params.fileId, {
11241
+ container_id: params.containerId
11242
+ }),
11243
+ "openai-containers"
11244
+ );
11245
+ return new Uint8Array(await response.arrayBuffer());
11246
+ }
11247
+ async function downloadOpenAiContainerFileText(params) {
11248
+ const bytes = await downloadOpenAiContainerFile(params);
11249
+ return new TextDecoder().decode(bytes);
11250
+ }
11251
+
11094
11252
  // src/agent.ts
11095
11253
  var import_node_crypto4 = require("crypto");
11096
11254
  var import_node_path9 = __toESM(require("path"), 1);
@@ -14891,12 +15049,16 @@ async function runCandidateEvolution(options) {
14891
15049
  createListDirectoryTool,
14892
15050
  createModelAgnosticFilesystemToolSet,
14893
15051
  createNodeAgentFilesystem,
15052
+ createOpenAiContainer,
14894
15053
  createReplaceTool,
14895
15054
  createRgSearchTool,
14896
15055
  createToolLoopSteeringChannel,
14897
15056
  createViewImageTool,
14898
15057
  createWriteFileTool,
14899
15058
  customTool,
15059
+ deleteOpenAiContainer,
15060
+ downloadOpenAiContainerFile,
15061
+ downloadOpenAiContainerFileText,
14900
15062
  emptyFileUploadMetrics,
14901
15063
  encodeChatGptAuthJson,
14902
15064
  encodeChatGptAuthJsonB64,
@@ -14921,6 +15083,7 @@ async function runCandidateEvolution(options) {
14921
15083
  isLlmTextModelId,
14922
15084
  isOpenAiImageModelId,
14923
15085
  isOpenAiModelId,
15086
+ listOpenAiContainerFiles,
14924
15087
  loadEnvFromFile,
14925
15088
  loadLocalEnv,
14926
15089
  parseJsonFromLlmText,
@@ -14941,6 +15104,7 @@ async function runCandidateEvolution(options) {
14941
15104
  stripCodexCitationMarkers,
14942
15105
  toGeminiJsonSchema,
14943
15106
  tool,
15107
+ uploadOpenAiContainerFile,
14944
15108
  validateOpenAiGptImage2Resolution
14945
15109
  });
14946
15110
  //# sourceMappingURL=index.cjs.map