@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 +1 -1
- package/dist/bin/ogtool.cjs +111 -23
- package/dist/cli.d.ts +7 -0
- package/package.json +3 -2
- package/src/cli.ts +17 -4
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`
|
package/dist/bin/ogtool.cjs
CHANGED
|
@@ -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
|
-
|
|
15275
|
-
|
|
15276
|
-
|
|
15277
|
-
|
|
15278
|
-
|
|
15279
|
-
|
|
15280
|
-
|
|
15281
|
-
|
|
15282
|
-
|
|
15283
|
-
|
|
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
|
-
|
|
15317
|
-
|
|
15318
|
-
|
|
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.
|
|
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
|
|
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(
|
|
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(
|
|
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
|
+
"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.
|
|
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
|
|
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(
|
|
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(
|
|
247
|
+
process.stdout.write(callOutput(result, full));
|
|
235
248
|
return;
|
|
236
249
|
}
|
|
237
250
|
if (command === "declarations") {
|