@opengeni/ogtool 0.3.43 → 0.3.44-canary.36079201993001

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
@@ -80,10 +80,11 @@ the total/offset footer. Both `--flag value` and `--flag=value` are supported.
80
80
  `--full` rejects `--json`, `--query`, `--limit`, and `--offset`, including explicitly
81
81
  supplied defaults. Unknown/duplicate flags and extra arguments are errors.
82
82
  `show` accepts the same exact path/model-name/identity aliases as `call`
83
- and rejects unknown or ambiguous names. Its JSON output (including the final newline)
84
- is limited to 64 KiB; oversized details fail without partial output or schema
85
- truncation. Use `list --full` redirected to a file, or `declarations <output-file>`,
86
- for larger schemas. Existing scripts parsing the old `list` JSON must use `list --full`.
83
+ and rejects unknown or ambiguous names. It returns the complete single-tool JSON,
84
+ including large schemas. To keep a large schema out of agent context, redirect
85
+ `ogtool show <path> > tool.json` and read only the needed file ranges; use
86
+ `ogtool declarations <output-file>` for generated TypeScript types. Existing
87
+ scripts parsing the old `list` JSON must use `list --full`.
87
88
 
88
89
  The Connected Machine fallback, `"$OPENGENI_CODEMODE_NATIVE_CLIENT" codemode`,
89
90
  supports the same `list`, `list --json`, `list --full`, and `show` discovery behavior.
@@ -14731,7 +14731,15 @@ function generateToolDeclarations(catalog, options) {
14731
14731
  return [
14732
14732
  `// Generated by ${options.generatedBy}. Do not edit.`,
14733
14733
  `// ${options.catalogDigestLabel ?? "Tool catalog digest"}: ${catalog.digest}`,
14734
- `import type { ${options.callOptionsType}, ${options.fallbackResultType} } from ${JSON.stringify(options.moduleSpecifier)};`,
14734
+ ...options.typedResultWrapperType ? [
14735
+ "// Typed calls normally return their structured output. If image blocks are present,",
14736
+ "// they return CodemodeMultimodalValue<T> with local image paths and original content."
14737
+ ] : [],
14738
+ `import type { ${[
14739
+ options.callOptionsType,
14740
+ options.fallbackResultType,
14741
+ ...options.typedResultWrapperType ? [options.typedResultWrapperType] : []
14742
+ ].join(", ")} } from ${JSON.stringify(options.moduleSpecifier)};`,
14735
14743
  "",
14736
14744
  `declare module ${JSON.stringify(options.moduleSpecifier)} {`,
14737
14745
  ` interface ${options.interfaceName} {`,
@@ -14776,7 +14784,8 @@ function renderChildren(node, indent, options) {
14776
14784
  if (child.entry) {
14777
14785
  const entry = child.entry;
14778
14786
  const input = jsonSchemaToTypeScript(entry.inputSchema);
14779
- const output = entry.outputSchema ? jsonSchemaToTypeScript(entry.outputSchema) : options.fallbackResultType;
14787
+ const structuredOutput = entry.outputSchema ? jsonSchemaToTypeScript(entry.outputSchema) : null;
14788
+ const output = structuredOutput ? options.typedResultWrapperType ? `${structuredOutput} | ${options.typedResultWrapperType}<${structuredOutput}>` : structuredOutput : options.fallbackResultType;
14780
14789
  const description = boundedDoc(entry.description ?? entry.title);
14781
14790
  if (description)
14782
14791
  lines.push(...renderDoc(description, indent));
@@ -14943,6 +14952,103 @@ function spaces(count) {
14943
14952
  function isObject2(value) {
14944
14953
  return value !== null && typeof value === "object" && !Array.isArray(value);
14945
14954
  }
14955
+
14956
+ // ../codemode/src/images.ts
14957
+ var import_promises = require("node:fs/promises");
14958
+ var import_node_os = require("node:os");
14959
+ var import_node_path = require("node:path");
14960
+ var MAX_IMAGE_BASE64_CHARS = 16 * 1024 * 1024;
14961
+ var TEMP_IMAGE_PREFIX = "opengeni-codemode-image-";
14962
+ var TEMP_IMAGE_MAX_AGE_MS = 24 * 60 * 60 * 1000;
14963
+ var TEMP_IMAGE_PRUNE_INTERVAL_MS = 60 * 60 * 1000;
14964
+ var lastPruneAt = 0;
14965
+ async function materializeCodemodeImages(result, options = {}) {
14966
+ const blocks = result.content.filter((content) => content.type === "image");
14967
+ if (options.requireOne && blocks.length !== 1) {
14968
+ throw new Error("Codemode browser screenshot must return exactly one image block");
14969
+ }
14970
+ if (blocks.length === 0)
14971
+ return [];
14972
+ if (options.saveTo !== undefined && blocks.length !== 1) {
14973
+ throw new Error("An explicit image path requires exactly one image block");
14974
+ }
14975
+ let totalBase64Chars = 0;
14976
+ const decoded = blocks.map((block) => {
14977
+ const mimeType = supportedMimeType(block.mimeType);
14978
+ totalBase64Chars += block.data.length;
14979
+ if (block.data.length === 0 || block.data.length > MAX_IMAGE_BASE64_CHARS || totalBase64Chars > MAX_IMAGE_BASE64_CHARS) {
14980
+ throw new Error("Codemode image exceeds the Code Mode result limit");
14981
+ }
14982
+ const bytes = Buffer.from(block.data, "base64");
14983
+ if (bytes.length === 0 || bytes.toString("base64") !== block.data || !matchesImageType(bytes, mimeType)) {
14984
+ throw new Error("Codemode image bytes are invalid");
14985
+ }
14986
+ return { bytes, mimeType };
14987
+ });
14988
+ let directory;
14989
+ if (options.saveTo === undefined) {
14990
+ await pruneExpiredPrivateImages();
14991
+ directory = await import_promises.mkdtemp(import_node_path.join(import_node_os.tmpdir(), TEMP_IMAGE_PREFIX));
14992
+ } else {
14993
+ if (options.saveTo.length === 0)
14994
+ throw new TypeError("Image saveTo must be a nonempty path");
14995
+ await import_promises.mkdir(import_node_path.dirname(import_node_path.resolve(options.saveTo)), { recursive: true });
14996
+ }
14997
+ const images = [];
14998
+ try {
14999
+ for (const [index, image] of decoded.entries()) {
15000
+ const extension = image.mimeType === "image/jpeg" ? "jpg" : image.mimeType.split("/")[1];
15001
+ const path = options.saveTo === undefined ? import_node_path.join(directory, decoded.length === 1 ? `frame.${extension}` : `image-${index + 1}.${extension}`) : import_node_path.resolve(options.saveTo);
15002
+ await import_promises.writeFile(path, image.bytes, { flag: "wx", mode: 384 });
15003
+ images.push({ path, mimeType: image.mimeType, sizeBytes: image.bytes.length });
15004
+ }
15005
+ } catch (error51) {
15006
+ if (directory)
15007
+ await import_promises.rm(directory, { recursive: true, force: true });
15008
+ throw error51;
15009
+ }
15010
+ return images;
15011
+ }
15012
+ async function pruneExpiredPrivateImages() {
15013
+ const now = Date.now();
15014
+ if (now - lastPruneAt < TEMP_IMAGE_PRUNE_INTERVAL_MS)
15015
+ return;
15016
+ lastPruneAt = now;
15017
+ if (typeof process.getuid !== "function")
15018
+ return;
15019
+ try {
15020
+ let removed = 0;
15021
+ for (const entry of await import_promises.readdir(import_node_os.tmpdir(), { withFileTypes: true })) {
15022
+ if (!entry.isDirectory() || !/^opengeni-codemode-image-[A-Za-z0-9]{6}$/u.test(entry.name)) {
15023
+ continue;
15024
+ }
15025
+ const path = import_node_path.join(import_node_os.tmpdir(), entry.name);
15026
+ const info = await import_promises.lstat(path);
15027
+ if (!info.isDirectory() || info.uid !== process.getuid() || (info.mode & 63) !== 0 || now - info.mtimeMs < TEMP_IMAGE_MAX_AGE_MS) {
15028
+ continue;
15029
+ }
15030
+ await import_promises.rm(path, { recursive: true, force: true });
15031
+ if (++removed >= 64)
15032
+ break;
15033
+ }
15034
+ } catch {}
15035
+ }
15036
+ function supportedMimeType(mimeType) {
15037
+ if (mimeType === "image/png" || mimeType === "image/jpeg" || mimeType === "image/webp") {
15038
+ return mimeType;
15039
+ }
15040
+ throw new Error(`Codemode image has unsupported type: ${mimeType}`);
15041
+ }
15042
+ function matchesImageType(bytes, mimeType) {
15043
+ switch (mimeType) {
15044
+ case "image/png":
15045
+ return bytes.length >= 8 && bytes.subarray(0, 8).equals(Buffer.from("89504e470d0a1a0a", "hex"));
15046
+ case "image/jpeg":
15047
+ return bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255;
15048
+ case "image/webp":
15049
+ return bytes.length >= 12 && bytes.toString("ascii", 0, 4) === "RIFF" && bytes.toString("ascii", 8, 12) === "WEBP";
15050
+ }
15051
+ }
14946
15052
  // ../codemode/src/declarations.ts
14947
15053
  function generateCodemodeDeclarations(catalog, options = {}) {
14948
15054
  const verified = parseVerifiedAttemptToolCatalog(catalog);
@@ -14951,6 +15057,7 @@ function generateCodemodeDeclarations(catalog, options = {}) {
14951
15057
  interfaceName: "CodemodeGeneratedTools",
14952
15058
  callOptionsType: "CodemodeCallOptions",
14953
15059
  fallbackResultType: "CodemodeToolResult",
15060
+ typedResultWrapperType: "CodemodeMultimodalValue",
14954
15061
  generatedBy: "@opengeni/codemode via @opengeni/tool-gateway",
14955
15062
  catalogDigestLabel: "Attempt catalog digest"
14956
15063
  });
@@ -15132,7 +15239,15 @@ class CodemodeClient {
15132
15239
  if (!result.structuredContent) {
15133
15240
  throw new CodemodeToolContractError(`Codemode tool ${path.join(".")} declared outputSchema but returned no structured content`);
15134
15241
  }
15135
- return result.structuredContent;
15242
+ if (!result.content.some((content) => content.type === "image")) {
15243
+ return result.structuredContent;
15244
+ }
15245
+ const images2 = await materializeCodemodeImages(result);
15246
+ return {
15247
+ structuredContent: result.structuredContent,
15248
+ images: images2,
15249
+ otherContent: result.content.filter((content) => content.type !== "image")
15250
+ };
15136
15251
  }
15137
15252
  async submit(operationId, catalogDigest, identity, argumentsValue, signal) {
15138
15253
  const response = await this.request("/calls", {
@@ -15285,14 +15400,14 @@ function parseCodemodeApiError(input) {
15285
15400
  }
15286
15401
  async function abortableDelay(delayMs, signal) {
15287
15402
  if (!signal) {
15288
- await new Promise((resolve) => setTimeout(resolve, delayMs));
15403
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs));
15289
15404
  return;
15290
15405
  }
15291
15406
  throwIfAborted(signal);
15292
- await new Promise((resolve, reject) => {
15407
+ await new Promise((resolve2, reject) => {
15293
15408
  const timer = setTimeout(() => {
15294
15409
  signal.removeEventListener("abort", onAbort);
15295
- resolve();
15410
+ resolve2();
15296
15411
  }, delayMs);
15297
15412
  const onAbort = () => {
15298
15413
  clearTimeout(timer);
@@ -15304,7 +15419,7 @@ async function abortableDelay(delayMs, signal) {
15304
15419
  // package.json
15305
15420
  var package_default = {
15306
15421
  name: "@opengeni/ogtool",
15307
- version: "0.3.43",
15422
+ version: "0.3.44-canary.36079201993001",
15308
15423
  description: "Codemode CLI and typed client entrypoint for an OpenGeni execution attempt.",
15309
15424
  license: "Apache-2.0",
15310
15425
  repository: {
@@ -15552,14 +15667,10 @@ function listProjection(catalog) {
15552
15667
  }))
15553
15668
  };
15554
15669
  }
15555
- var SHOW_MAX_BYTES = 64 * 1024;
15556
15670
  function showOutput(catalog, name) {
15557
15671
  const entry = resolveTool(catalog, name);
15558
15672
  const projection = listProjection({ ...catalog, entries: [entry] });
15559
15673
  const output = JSON.stringify(projection.tools[0], null, 2);
15560
- if (Buffer.byteLength(output, "utf8") + 1 > SHOW_MAX_BYTES) {
15561
- throw new Error("Tool details exceed 65536 bytes; use list --full or declarations <output-file>");
15562
- }
15563
15674
  return `${output}
15564
15675
  `;
15565
15676
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/ogtool",
3
- "version": "0.3.43",
3
+ "version": "0.3.44-canary.36079201993001",
4
4
  "description": "Codemode CLI and typed client entrypoint for an OpenGeni execution attempt.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
38
38
  },
39
39
  "dependencies": {
40
- "@opengeni/codemode": "^0.6.1"
40
+ "@opengeni/codemode": "^0.6.2-canary.36079201993001"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=18"
package/src/cli.ts CHANGED
@@ -153,17 +153,10 @@ export function listProjection(catalog: AttemptToolCatalog): Record<string, unkn
153
153
  };
154
154
  }
155
155
 
156
- const SHOW_MAX_BYTES = 64 * 1024;
157
-
158
156
  export function showOutput(catalog: AttemptToolCatalog, name: string): string {
159
157
  const entry = resolveTool(catalog, name);
160
158
  const projection = listProjection({ ...catalog, entries: [entry] });
161
159
  const output = JSON.stringify((projection.tools as unknown[])[0], null, 2);
162
- if (Buffer.byteLength(output, "utf8") + 1 > SHOW_MAX_BYTES) {
163
- throw new Error(
164
- "Tool details exceed 65536 bytes; use list --full or declarations <output-file>",
165
- );
166
- }
167
160
  return `${output}\n`;
168
161
  }
169
162