@ljoukov/llm 7.0.14 → 7.0.15

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);
@@ -6462,6 +6468,50 @@ function toOpenAiTools(tools, options) {
6462
6468
  }
6463
6469
  });
6464
6470
  }
6471
+ function extractOpenAiResponseMetadata(response) {
6472
+ if (!response || typeof response !== "object") {
6473
+ return void 0;
6474
+ }
6475
+ const record = response;
6476
+ const responseId = typeof record.id === "string" ? record.id : void 0;
6477
+ const output = Array.isArray(record.output) ? record.output : [];
6478
+ const containers = [];
6479
+ const seen = /* @__PURE__ */ new Set();
6480
+ const addContainer = (container) => {
6481
+ const key = `${container.toolType}:${container.containerId}:${container.itemId ?? ""}:${container.callId ?? ""}`;
6482
+ if (seen.has(key)) {
6483
+ return;
6484
+ }
6485
+ seen.add(key);
6486
+ containers.push(container);
6487
+ };
6488
+ for (const item of output) {
6489
+ if (!item || typeof item !== "object") {
6490
+ continue;
6491
+ }
6492
+ const itemRecord = item;
6493
+ const itemId = typeof itemRecord.id === "string" ? itemRecord.id : void 0;
6494
+ const callId = typeof itemRecord.call_id === "string" ? itemRecord.call_id : void 0;
6495
+ if (itemRecord.type === "shell_call") {
6496
+ const environment = itemRecord.environment && typeof itemRecord.environment === "object" ? itemRecord.environment : void 0;
6497
+ const containerId = environment?.type === "container_reference" && typeof environment.container_id === "string" ? environment.container_id : void 0;
6498
+ if (containerId) {
6499
+ addContainer({ containerId, toolType: "shell", itemId, callId });
6500
+ }
6501
+ continue;
6502
+ }
6503
+ if (itemRecord.type === "code_interpreter_call") {
6504
+ const containerId = typeof itemRecord.container_id === "string" ? itemRecord.container_id : void 0;
6505
+ if (containerId) {
6506
+ addContainer({ containerId, toolType: "code_interpreter", itemId, callId });
6507
+ }
6508
+ }
6509
+ }
6510
+ if (!responseId && containers.length === 0) {
6511
+ return void 0;
6512
+ }
6513
+ return { ...responseId ? { responseId } : {}, containers };
6514
+ }
6465
6515
  function mergeTokenUpdates(current, next) {
6466
6516
  if (!next) {
6467
6517
  return current;
@@ -8073,6 +8123,7 @@ async function runTextCall(params) {
8073
8123
  let modelVersion = request.model;
8074
8124
  let blocked = false;
8075
8125
  let grounding;
8126
+ let openAi;
8076
8127
  const responseParts = [];
8077
8128
  let responseRole;
8078
8129
  let latestUsage;
@@ -8183,6 +8234,7 @@ async function runTextCall(params) {
8183
8234
  }
8184
8235
  }
8185
8236
  const finalResponse = await stream.finalResponse();
8237
+ openAi = extractOpenAiResponseMetadata(finalResponse);
8186
8238
  modelVersion = typeof finalResponse.model === "string" ? finalResponse.model : request.model;
8187
8239
  pushEvent({ type: "model", modelVersion });
8188
8240
  if (finalResponse.error) {
@@ -8426,6 +8478,7 @@ async function runTextCall(params) {
8426
8478
  costUsd,
8427
8479
  usage: latestUsage,
8428
8480
  grounding: grounding ? sanitiseLogValue(grounding) : void 0,
8481
+ openAi: openAi ? sanitiseLogValue(openAi) : void 0,
8429
8482
  responseChars: text.length,
8430
8483
  thoughtChars: thoughts.length,
8431
8484
  responseImages,
@@ -8442,7 +8495,8 @@ async function runTextCall(params) {
8442
8495
  blocked,
8443
8496
  usage: latestUsage,
8444
8497
  costUsd,
8445
- grounding
8498
+ grounding,
8499
+ openAi
8446
8500
  };
8447
8501
  } catch (error) {
8448
8502
  const partialParts = mergeConsecutiveTextParts(responseParts);
@@ -11091,6 +11145,104 @@ function appendMarkdownSourcesSection(value, sources) {
11091
11145
  ${lines}`;
11092
11146
  }
11093
11147
 
11148
+ // src/openai/containers.ts
11149
+ var import_openai4 = require("openai");
11150
+ function toOpenAiContainerNetworkPolicy(policy) {
11151
+ if (!policy) {
11152
+ return void 0;
11153
+ }
11154
+ if (policy.type === "disabled") {
11155
+ return { type: "disabled" };
11156
+ }
11157
+ return {
11158
+ type: "allowlist",
11159
+ allowed_domains: Array.from(policy.allowedDomains),
11160
+ ...policy.domainSecrets ? {
11161
+ domain_secrets: policy.domainSecrets.map((secret) => ({
11162
+ domain: secret.domain,
11163
+ name: secret.name,
11164
+ value: secret.value
11165
+ }))
11166
+ } : {}
11167
+ };
11168
+ }
11169
+ function toContainer(container) {
11170
+ return {
11171
+ id: String(container.id),
11172
+ name: typeof container.name === "string" ? container.name : "",
11173
+ status: typeof container.status === "string" ? container.status : "",
11174
+ ...typeof container.created_at === "number" ? { createdAt: container.created_at } : {},
11175
+ ...typeof container.last_active_at === "number" ? { lastActiveAt: container.last_active_at } : {},
11176
+ ...typeof container.memory_limit === "string" ? { memoryLimit: container.memory_limit } : {}
11177
+ };
11178
+ }
11179
+ function toContainerFile(file) {
11180
+ return {
11181
+ id: String(file.id),
11182
+ containerId: typeof file.container_id === "string" ? file.container_id : "",
11183
+ path: typeof file.path === "string" ? file.path : "",
11184
+ ...typeof file.bytes === "number" || file.bytes === null ? { bytes: file.bytes } : {},
11185
+ ...typeof file.created_at === "number" ? { createdAt: file.created_at } : {},
11186
+ ...typeof file.source === "string" ? { source: file.source } : {}
11187
+ };
11188
+ }
11189
+ async function createOpenAiContainer(options) {
11190
+ const container = await runOpenAiCall(
11191
+ async (client) => await client.containers.create({
11192
+ name: options.name,
11193
+ ...options.fileIds ? { file_ids: Array.from(options.fileIds) } : {},
11194
+ ...options.memoryLimit ? { memory_limit: options.memoryLimit } : {},
11195
+ ...options.networkPolicy ? { network_policy: toOpenAiContainerNetworkPolicy(options.networkPolicy) } : {},
11196
+ ...options.expiresAfterMinutes ? {
11197
+ expires_after: {
11198
+ anchor: "last_active_at",
11199
+ minutes: options.expiresAfterMinutes
11200
+ }
11201
+ } : {}
11202
+ }),
11203
+ "openai-containers"
11204
+ );
11205
+ return toContainer(container);
11206
+ }
11207
+ async function deleteOpenAiContainer(containerId) {
11208
+ await runOpenAiCall(
11209
+ async (client) => await client.containers.delete(containerId),
11210
+ "openai-containers"
11211
+ );
11212
+ }
11213
+ async function listOpenAiContainerFiles(containerId) {
11214
+ const files2 = [];
11215
+ await runOpenAiCall(async (client) => {
11216
+ for await (const file of client.containers.files.list(containerId)) {
11217
+ files2.push(toContainerFile(file));
11218
+ }
11219
+ }, "openai-containers");
11220
+ return files2;
11221
+ }
11222
+ async function uploadOpenAiContainerFile(upload) {
11223
+ const file = await (0, import_openai4.toFile)(upload.data, upload.filename, {
11224
+ ...upload.mimeType ? { type: upload.mimeType } : {}
11225
+ });
11226
+ const created = await runOpenAiCall(
11227
+ async (client) => await client.containers.files.create(upload.containerId, { file }),
11228
+ "openai-containers"
11229
+ );
11230
+ return toContainerFile(created);
11231
+ }
11232
+ async function downloadOpenAiContainerFile(params) {
11233
+ const response = await runOpenAiCall(
11234
+ async (client) => await client.containers.files.content.retrieve(params.fileId, {
11235
+ container_id: params.containerId
11236
+ }),
11237
+ "openai-containers"
11238
+ );
11239
+ return new Uint8Array(await response.arrayBuffer());
11240
+ }
11241
+ async function downloadOpenAiContainerFileText(params) {
11242
+ const bytes = await downloadOpenAiContainerFile(params);
11243
+ return new TextDecoder().decode(bytes);
11244
+ }
11245
+
11094
11246
  // src/agent.ts
11095
11247
  var import_node_crypto4 = require("crypto");
11096
11248
  var import_node_path9 = __toESM(require("path"), 1);
@@ -14891,12 +15043,16 @@ async function runCandidateEvolution(options) {
14891
15043
  createListDirectoryTool,
14892
15044
  createModelAgnosticFilesystemToolSet,
14893
15045
  createNodeAgentFilesystem,
15046
+ createOpenAiContainer,
14894
15047
  createReplaceTool,
14895
15048
  createRgSearchTool,
14896
15049
  createToolLoopSteeringChannel,
14897
15050
  createViewImageTool,
14898
15051
  createWriteFileTool,
14899
15052
  customTool,
15053
+ deleteOpenAiContainer,
15054
+ downloadOpenAiContainerFile,
15055
+ downloadOpenAiContainerFileText,
14900
15056
  emptyFileUploadMetrics,
14901
15057
  encodeChatGptAuthJson,
14902
15058
  encodeChatGptAuthJsonB64,
@@ -14921,6 +15077,7 @@ async function runCandidateEvolution(options) {
14921
15077
  isLlmTextModelId,
14922
15078
  isOpenAiImageModelId,
14923
15079
  isOpenAiModelId,
15080
+ listOpenAiContainerFiles,
14924
15081
  loadEnvFromFile,
14925
15082
  loadLocalEnv,
14926
15083
  parseJsonFromLlmText,
@@ -14941,6 +15098,7 @@ async function runCandidateEvolution(options) {
14941
15098
  stripCodexCitationMarkers,
14942
15099
  toGeminiJsonSchema,
14943
15100
  tool,
15101
+ uploadOpenAiContainerFile,
14944
15102
  validateOpenAiGptImage2Resolution
14945
15103
  });
14946
15104
  //# sourceMappingURL=index.cjs.map