@opengeni/ogtool 1.3.0 → 1.4.2

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
@@ -44,7 +44,7 @@ Commands:
44
44
  - `ogtool list [--json] [--query <substring>] [--limit <1..100>] [--offset <integer>]`
45
45
  - `ogtool list --full` — the previous full catalog JSON, including identity and schemas
46
46
  - `ogtool show <tool-path-or-model-name>` — one tool's details and schemas as JSON
47
- - `ogtool call <tool-path-or-model-name> [json-object]`
47
+ - `ogtool call <tool-path-or-model-name> [json-object] [--full]` — the tool result as JSON. A text block that only repeats `structuredContent` as JSON is omitted, so the payload prints once; prose, differing text, annotated text, and non-text blocks stay. `--full` prints the exact result.
48
48
  - `ogtool declarations [output-file]`
49
49
  - `ogtool doctor`
50
50
  - `ogtool --version`
@@ -40,6 +40,7 @@ var __export = (target, all) => {
40
40
  // src/cli.ts
41
41
  var exports_cli = {};
42
42
  __export(exports_cli, {
43
+ callOutput: () => callOutput,
43
44
  listProjection: () => listProjection,
44
45
  parseToolArguments: () => parseToolArguments,
45
46
  resolveTool: () => resolveTool,
@@ -14676,6 +14677,65 @@ var CodemodeDispatchAck = exports_external.object({
14676
14677
  status: exports_external.enum(["accepted", "already_running", "terminal", "unavailable", "rejected"])
14677
14678
  }).strict();
14678
14679
 
14680
+ // ../contracts/src/mcp-structured-content.ts
14681
+ function omitStructuredContentTextDuplicates(result) {
14682
+ const structured = result.structuredContent;
14683
+ if (structured === undefined || structured === null)
14684
+ return result;
14685
+ const content = result.content.filter((entry) => !isPlainTextBlock(entry) || !textDuplicatesStructuredContent(entry.text, structured));
14686
+ if (content.length === result.content.length)
14687
+ return result;
14688
+ return { ...result, content };
14689
+ }
14690
+ function isPlainTextBlock(entry) {
14691
+ if (!entry || typeof entry !== "object" || Array.isArray(entry))
14692
+ return false;
14693
+ const record2 = entry;
14694
+ if (record2.type !== "text" || typeof record2.text !== "string")
14695
+ return false;
14696
+ return Object.keys(record2).every((key) => key === "type" || key === "text");
14697
+ }
14698
+ function textDuplicatesStructuredContent(text, structured) {
14699
+ const trimmed = text.replace(/^[ \t\n\r]+/u, "");
14700
+ if (!trimmed.startsWith("{") && !trimmed.startsWith("["))
14701
+ return false;
14702
+ let parsed;
14703
+ try {
14704
+ parsed = JSON.parse(text);
14705
+ } catch {
14706
+ return false;
14707
+ }
14708
+ return !containsUnsafeInteger(parsed) && jsonValuesEqual(parsed, structured);
14709
+ }
14710
+ function containsUnsafeInteger(value) {
14711
+ if (typeof value === "number")
14712
+ return Number.isInteger(value) && !Number.isSafeInteger(value);
14713
+ if (Array.isArray(value))
14714
+ return value.some(containsUnsafeInteger);
14715
+ if (value && typeof value === "object")
14716
+ return Object.values(value).some(containsUnsafeInteger);
14717
+ return false;
14718
+ }
14719
+ function jsonValuesEqual(left, right) {
14720
+ if (left === right)
14721
+ return true;
14722
+ if (typeof left !== "object" || typeof right !== "object" || left === null || right === null) {
14723
+ return false;
14724
+ }
14725
+ if (Array.isArray(left) || Array.isArray(right)) {
14726
+ if (!Array.isArray(left) || !Array.isArray(right) || left.length !== right.length) {
14727
+ return false;
14728
+ }
14729
+ return left.every((entry, index) => jsonValuesEqual(entry, right[index]));
14730
+ }
14731
+ const leftRecord = left;
14732
+ const rightRecord = right;
14733
+ const leftKeys = Object.keys(leftRecord);
14734
+ if (leftKeys.length !== Object.keys(rightRecord).length)
14735
+ return false;
14736
+ return leftKeys.every((key) => Object.hasOwn(rightRecord, key) && jsonValuesEqual(leftRecord[key], rightRecord[key]));
14737
+ }
14738
+
14679
14739
  // ../tool-gateway/src/catalog.ts
14680
14740
  var import_node_crypto = require("node:crypto");
14681
14741
 
@@ -15271,19 +15331,21 @@ class CodemodeClient {
15271
15331
  };
15272
15332
  }
15273
15333
  async submit(operationId, catalogDigest, identity, argumentsValue, signal) {
15274
- const response = await this.request("/calls", {
15275
- method: "POST",
15276
- ...signal ? { signal } : {},
15277
- headers: { "content-type": "application/json" },
15278
- body: JSON.stringify({
15279
- operationId,
15280
- catalogDigest,
15281
- identity,
15282
- arguments: argumentsValue,
15283
- durableApproval: true
15284
- })
15334
+ return await retryTransientCodemodeRequest(signal, async () => {
15335
+ const response = await this.request("/calls", {
15336
+ method: "POST",
15337
+ ...signal ? { signal } : {},
15338
+ headers: { "content-type": "application/json" },
15339
+ body: JSON.stringify({
15340
+ operationId,
15341
+ catalogDigest,
15342
+ identity,
15343
+ arguments: argumentsValue,
15344
+ durableApproval: true
15345
+ })
15346
+ });
15347
+ return CodemodeCallSubmission.parse(await response.json()).operation;
15285
15348
  });
15286
- return CodemodeCallSubmission.parse(await response.json()).operation;
15287
15349
  }
15288
15350
  async status(operationId, signal) {
15289
15351
  const operation = await this.read(operationId, signal);
@@ -15313,11 +15375,13 @@ class CodemodeClient {
15313
15375
  }
15314
15376
  }
15315
15377
  async read(operationId, signal) {
15316
- const response = await this.request(`/calls/${operationId}`, {
15317
- method: "GET",
15318
- ...signal ? { signal } : {}
15378
+ return await retryTransientCodemodeRequest(signal, async () => {
15379
+ const response = await this.request(`/calls/${operationId}`, {
15380
+ method: "GET",
15381
+ ...signal ? { signal } : {}
15382
+ });
15383
+ return CodemodeOperation.parse(await response.json());
15319
15384
  });
15320
- return CodemodeOperation.parse(await response.json());
15321
15385
  }
15322
15386
  async sessionRequest(path, init) {
15323
15387
  siteSessionPath(path, "site-host", init.method ?? "GET");
@@ -15448,6 +15512,23 @@ function parseCodemodeApiError(input) {
15448
15512
  ...details ? { details } : {}
15449
15513
  } : null;
15450
15514
  }
15515
+ var CODEMODE_TRANSIENT_RETRY_DELAYS_MS = [100, 250, 500, 1000, 2000];
15516
+ function isTransientCodemodeTransportError(error51) {
15517
+ return error51 instanceof CodemodeTransportError && error51.status !== null && error51.status >= 500 && error51.retryable === true && error51.outcomeUnknown !== true;
15518
+ }
15519
+ async function retryTransientCodemodeRequest(signal, request) {
15520
+ for (let attempt = 0;; attempt += 1) {
15521
+ try {
15522
+ return await request();
15523
+ } catch (error51) {
15524
+ const delayMs = CODEMODE_TRANSIENT_RETRY_DELAYS_MS[attempt];
15525
+ if (delayMs === undefined || signal?.aborted || !isTransientCodemodeTransportError(error51)) {
15526
+ throw error51;
15527
+ }
15528
+ await abortableDelay(delayMs + Math.floor(Math.random() * delayMs), signal);
15529
+ }
15530
+ }
15531
+ }
15451
15532
  async function abortableDelay(delayMs, signal) {
15452
15533
  if (!signal) {
15453
15534
  await new Promise((resolve2) => setTimeout(resolve2, delayMs));
@@ -15469,7 +15550,7 @@ async function abortableDelay(delayMs, signal) {
15469
15550
  // package.json
15470
15551
  var package_default = {
15471
15552
  name: "@opengeni/ogtool",
15472
- version: "1.3.0",
15553
+ version: "1.4.2",
15473
15554
  description: "Codemode CLI and typed client entrypoint for an OpenGeni execution attempt.",
15474
15555
  license: "Apache-2.0",
15475
15556
  repository: {
@@ -15506,7 +15587,8 @@ var package_default = {
15506
15587
  prepublishOnly: "bash ../../scripts/prepublish-guard"
15507
15588
  },
15508
15589
  dependencies: {
15509
- "@opengeni/codemode": "workspace:*"
15590
+ "@opengeni/codemode": "workspace:*",
15591
+ "@opengeni/contracts": "workspace:*"
15510
15592
  },
15511
15593
  devDependencies: {
15512
15594
  tsup: "^8.5.0",
@@ -15598,7 +15680,7 @@ function usage(exitCode = 1) {
15598
15680
  " ogtool list [--full | --json]",
15599
15681
  " [--query <substring>] [--limit <1..100>] [--offset <nonnegative integer>]",
15600
15682
  " ogtool show <tool-path-or-model-name>",
15601
- " ogtool call <tool-path-or-model-name> [json-object]",
15683
+ " ogtool call <tool-path-or-model-name> [json-object] [--full]",
15602
15684
  " ogtool read <operation-id>",
15603
15685
  " ogtool resume <operation-id>",
15604
15686
  " ogtool declarations [output-file]",
@@ -15606,6 +15688,7 @@ function usage(exitCode = 1) {
15606
15688
  " ogtool --version",
15607
15689
  "",
15608
15690
  "list returns all authorized tools by default; --limit/--offset are opt-in slices",
15691
+ "call omits a text block that only repeats structuredContent; --full prints the exact result",
15609
15692
  "",
15610
15693
  "requires OPENGENI_CODEMODE_URL and OPENGENI_CODEMODE_TOKEN or OPENGENI_CODEMODE_TOKEN_FILE"
15611
15694
  ].join(`
@@ -15726,6 +15809,11 @@ function showOutput(catalog, name) {
15726
15809
  return `${output}
15727
15810
  `;
15728
15811
  }
15812
+ function callOutput(result, full = false) {
15813
+ const printed = full ? result : omitStructuredContentTextDuplicates(result);
15814
+ return `${JSON.stringify(printed, null, 2)}
15815
+ `;
15816
+ }
15729
15817
  function parseToolArguments(raw) {
15730
15818
  if (raw === undefined)
15731
15819
  return {};
@@ -15784,17 +15872,17 @@ async function main() {
15784
15872
  return;
15785
15873
  }
15786
15874
  if (command === "call") {
15787
- const name = process.argv[3];
15875
+ const full = args.includes("--full");
15876
+ const [name, rawArguments] = args.filter((arg) => arg !== "--full");
15788
15877
  if (!name)
15789
15878
  throw new Error("tool path or model name is required");
15790
- const argumentsValue = parseToolArguments(process.argv[4]);
15879
+ const argumentsValue = parseToolArguments(rawArguments);
15791
15880
  const catalog = await client.catalog();
15792
15881
  const entry = resolveTool(catalog, name);
15793
15882
  const result = await client.call(entry.identity, argumentsValue, {
15794
15883
  operationId: import_node_crypto3.randomUUID()
15795
15884
  });
15796
- process.stdout.write(`${JSON.stringify(result, null, 2)}
15797
- `);
15885
+ process.stdout.write(callOutput(result, full));
15798
15886
  return;
15799
15887
  }
15800
15888
  if (command === "declarations") {
package/dist/cli.d.ts CHANGED
@@ -1,5 +1,12 @@
1
1
  import { type AttemptToolCatalog, type AttemptToolCatalogEntry } from "@opengeni/codemode";
2
+ import { type AttemptToolResult } from "@opengeni/contracts";
2
3
  export declare function resolveTool(catalog: AttemptToolCatalog, name: string): AttemptToolCatalogEntry;
3
4
  export declare function listProjection(catalog: AttemptToolCatalog): Record<string, unknown>;
4
5
  export declare function showOutput(catalog: AttemptToolCatalog, name: string): string;
6
+ /**
7
+ * `call` prints the payload once by default: a text block that only repeats
8
+ * `structuredContent` as JSON is omitted. `--full` prints the exact result.
9
+ * The native client (`codemode call`) applies the same rule.
10
+ */
11
+ export declare function callOutput(result: AttemptToolResult, full?: boolean): string;
5
12
  export declare function parseToolArguments(raw: string | undefined): Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/ogtool",
3
- "version": "1.3.0",
3
+ "version": "1.4.2",
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,8 @@
37
37
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
38
38
  },
39
39
  "dependencies": {
40
- "@opengeni/codemode": "1.3.0"
40
+ "@opengeni/codemode": "1.4.2",
41
+ "@opengeni/contracts": "1.4.2"
41
42
  },
42
43
  "engines": {
43
44
  "node": ">=18"
package/src/cli.ts CHANGED
@@ -8,6 +8,7 @@ import {
8
8
  type AttemptToolCatalog,
9
9
  type AttemptToolCatalogEntry,
10
10
  } from "@opengeni/codemode";
11
+ import { omitStructuredContentTextDuplicates, type AttemptToolResult } from "@opengeni/contracts";
11
12
  import packageManifest from "../package.json" with { type: "json" };
12
13
  import { compactOutput, parseListOptions } from "./catalog-discovery";
13
14
 
@@ -20,7 +21,7 @@ function usage(exitCode = 1): void {
20
21
  " ogtool list [--full | --json]",
21
22
  " [--query <substring>] [--limit <1..100>] [--offset <nonnegative integer>]",
22
23
  " ogtool show <tool-path-or-model-name>",
23
- " ogtool call <tool-path-or-model-name> [json-object]",
24
+ " ogtool call <tool-path-or-model-name> [json-object] [--full]",
24
25
  " ogtool read <operation-id>",
25
26
  " ogtool resume <operation-id>",
26
27
  " ogtool declarations [output-file]",
@@ -28,6 +29,7 @@ function usage(exitCode = 1): void {
28
29
  " ogtool --version",
29
30
  "",
30
31
  "list returns all authorized tools by default; --limit/--offset are opt-in slices",
32
+ "call omits a text block that only repeats structuredContent; --full prints the exact result",
31
33
  "",
32
34
  "requires OPENGENI_CODEMODE_URL and OPENGENI_CODEMODE_TOKEN or OPENGENI_CODEMODE_TOKEN_FILE",
33
35
  ].join("\n");
@@ -164,6 +166,16 @@ export function showOutput(catalog: AttemptToolCatalog, name: string): string {
164
166
  return `${output}\n`;
165
167
  }
166
168
 
169
+ /**
170
+ * `call` prints the payload once by default: a text block that only repeats
171
+ * `structuredContent` as JSON is omitted. `--full` prints the exact result.
172
+ * The native client (`codemode call`) applies the same rule.
173
+ */
174
+ export function callOutput(result: AttemptToolResult, full = false): string {
175
+ const printed = full ? result : omitStructuredContentTextDuplicates(result);
176
+ return `${JSON.stringify(printed, null, 2)}\n`;
177
+ }
178
+
167
179
  export function parseToolArguments(raw: string | undefined): Record<string, unknown> {
168
180
  if (raw === undefined) return {};
169
181
  let value: unknown;
@@ -223,15 +235,16 @@ async function main(): Promise<void> {
223
235
  return;
224
236
  }
225
237
  if (command === "call") {
226
- const name = process.argv[3];
238
+ const full = args.includes("--full");
239
+ const [name, rawArguments] = args.filter((arg) => arg !== "--full");
227
240
  if (!name) throw new Error("tool path or model name is required");
228
- const argumentsValue = parseToolArguments(process.argv[4]);
241
+ const argumentsValue = parseToolArguments(rawArguments);
229
242
  const catalog = await client.catalog();
230
243
  const entry = resolveTool(catalog, name);
231
244
  const result = await client.call(entry.identity, argumentsValue, {
232
245
  operationId: randomUUID(),
233
246
  });
234
- process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
247
+ process.stdout.write(callOutput(result, full));
235
248
  return;
236
249
  }
237
250
  if (command === "declarations") {