@styx-api/core 0.5.3 → 0.6.1
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/dist/index.cjs +104 -20
- package/dist/index.d.cts +47 -5
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +47 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +104 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/backend/boutiques/boutiques.ts +19 -5
- package/src/backend/python/arg-builder.ts +8 -0
- package/src/backend/python/python.ts +102 -2
- package/src/backend/python/snippet.ts +54 -6
- package/src/backend/snippet-core.ts +32 -3
- package/src/frontend/boutiques/parser.ts +10 -2
package/dist/index.mjs
CHANGED
|
@@ -1242,12 +1242,17 @@ var BoutiquesParser = class {
|
|
|
1242
1242
|
wrapWithFlag(node, btInput) {
|
|
1243
1243
|
const flag = btInput["command-line-flag"];
|
|
1244
1244
|
if (!isString$2(flag)) return node;
|
|
1245
|
+
const flagSep = btInput["command-line-flag-separator"];
|
|
1246
|
+
const fused = isString$2(flagSep) && flagSep !== " ";
|
|
1245
1247
|
return {
|
|
1246
1248
|
kind: "sequence",
|
|
1247
|
-
attrs: {
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1249
|
+
attrs: {
|
|
1250
|
+
nodes: [{
|
|
1251
|
+
kind: "literal",
|
|
1252
|
+
attrs: { str: fused ? flag + flagSep : flag }
|
|
1253
|
+
}, node],
|
|
1254
|
+
...fused && { join: "" }
|
|
1255
|
+
}
|
|
1251
1256
|
};
|
|
1252
1257
|
}
|
|
1253
1258
|
wrapWithOptional(node) {
|
|
@@ -2762,7 +2767,7 @@ var BoutiquesEmitter = class {
|
|
|
2762
2767
|
}
|
|
2763
2768
|
if (peeled.flag) {
|
|
2764
2769
|
input["command-line-flag"] = peeled.flag;
|
|
2765
|
-
if (peeled.flagSeparator) input["command-line-flag-separator"] = peeled.flagSeparator;
|
|
2770
|
+
if (peeled.flagSeparator !== void 0) input["command-line-flag-separator"] = peeled.flagSeparator;
|
|
2766
2771
|
}
|
|
2767
2772
|
if (mapped.integer) input.integer = true;
|
|
2768
2773
|
if (mapped.minimum !== void 0) input.minimum = mapped.minimum;
|
|
@@ -2903,7 +2908,7 @@ var BoutiquesEmitter = class {
|
|
|
2903
2908
|
}
|
|
2904
2909
|
if (peeled.flag) {
|
|
2905
2910
|
input["command-line-flag"] = peeled.flag;
|
|
2906
|
-
if (peeled.flagSeparator) input["command-line-flag-separator"] = peeled.flagSeparator;
|
|
2911
|
+
if (peeled.flagSeparator !== void 0) input["command-line-flag-separator"] = peeled.flagSeparator;
|
|
2907
2912
|
}
|
|
2908
2913
|
if (mapped.integer) input.integer = true;
|
|
2909
2914
|
if (mapped.minimum !== void 0) input.minimum = mapped.minimum;
|
|
@@ -2972,9 +2977,11 @@ var BoutiquesEmitter = class {
|
|
|
2972
2977
|
const nodes = node.attrs.nodes;
|
|
2973
2978
|
if (nodes.length === 2 && nodes[0].kind === "literal") {
|
|
2974
2979
|
const flagLit = nodes[0].attrs.str;
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2980
|
+
if (node.attrs.join !== void 0) {
|
|
2981
|
+
const { flag, separator } = this.splitFlagLiteral(flagLit);
|
|
2982
|
+
result.flag = flag;
|
|
2983
|
+
result.flagSeparator = separator;
|
|
2984
|
+
} else result.flag = flagLit;
|
|
2978
2985
|
this.peelNodeInner(nodes[1], type, result);
|
|
2979
2986
|
}
|
|
2980
2987
|
break;
|
|
@@ -3495,6 +3502,10 @@ function resultToStmt$1(r) {
|
|
|
3495
3502
|
return isExpr$1(r) ? `cargs.append(${r.expr})` : r.stmt;
|
|
3496
3503
|
}
|
|
3497
3504
|
function appendLines$1(cb, code) {
|
|
3505
|
+
if (code.trim() === "") {
|
|
3506
|
+
cb.line("pass");
|
|
3507
|
+
return;
|
|
3508
|
+
}
|
|
3498
3509
|
for (const line of code.split("\n")) cb.line(line);
|
|
3499
3510
|
}
|
|
3500
3511
|
function toStringExpr$1(node, type, expr) {
|
|
@@ -5400,7 +5411,29 @@ function buildEmitModel(ctx, scope = new Scope(PY_RESERVED)) {
|
|
|
5400
5411
|
names.params = (rootType.kind === "struct" ? namedTypes.get(structKey(rootType)) : void 0) ?? (rootType.kind === "union" ? namedTypes.get(unionKey(rootType)) : void 0) ?? names.params;
|
|
5401
5412
|
const rootTypeTag = appId && pkg ? `${pkg}/${appId}` : void 0;
|
|
5402
5413
|
const paramsType = rootType.kind === "struct" || rootType.kind === "union" ? names.params : mapType$1(rootType, resolveTypeName(namedTypes));
|
|
5414
|
+
const hostName = (childScope, wireKey) => childScope.add(pyScrubIdent(snakeCase(wireKey), PY_RESERVED));
|
|
5415
|
+
const resolve = resolveTypeName(namedTypes);
|
|
5403
5416
|
const sigScope = scope.child(["params", "runner"]);
|
|
5417
|
+
const sigEntries = rootIsStruct && rootType.kind === "struct" ? buildSigEntries(rootType, collectFieldInfo(ctx, rootType), (wireKey) => hostName(sigScope, wireKey), pySigOptions(resolve)) : [];
|
|
5418
|
+
const rootStructKey = rootIsStruct && rootType.kind === "struct" ? structKey(rootType) : void 0;
|
|
5419
|
+
const nestedFactories = [];
|
|
5420
|
+
for (const decl of typeDecls) {
|
|
5421
|
+
if (decl.type.kind !== "struct") continue;
|
|
5422
|
+
const sKey = structKey(decl.type);
|
|
5423
|
+
if (sKey === rootStructKey) continue;
|
|
5424
|
+
const funcName = scope.add(pyScrubIdent(snakeCase(decl.name) + "_params", PY_RESERVED));
|
|
5425
|
+
const atType = decl.type.fields["@type"];
|
|
5426
|
+
const typeTag = atType && atType.kind === "literal" && typeof atType.value === "string" ? atType.value : void 0;
|
|
5427
|
+
const factoryScope = scope.child(["params"]);
|
|
5428
|
+
const factorySig = buildSigEntries(decl.type, collectFieldInfo(ctx, decl.type), (wireKey) => hostName(factoryScope, wireKey), pySigOptions(resolve));
|
|
5429
|
+
nestedFactories.push({
|
|
5430
|
+
typeName: decl.name,
|
|
5431
|
+
structKey: sKey,
|
|
5432
|
+
funcName,
|
|
5433
|
+
typeTag,
|
|
5434
|
+
sigEntries: factorySig
|
|
5435
|
+
});
|
|
5436
|
+
}
|
|
5404
5437
|
return {
|
|
5405
5438
|
appId,
|
|
5406
5439
|
pkg,
|
|
@@ -5411,13 +5444,14 @@ function buildEmitModel(ctx, scope = new Scope(PY_RESERVED)) {
|
|
|
5411
5444
|
typeDecls,
|
|
5412
5445
|
rootTypeTag,
|
|
5413
5446
|
paramsType,
|
|
5414
|
-
sigEntries
|
|
5447
|
+
sigEntries,
|
|
5448
|
+
nestedFactories
|
|
5415
5449
|
};
|
|
5416
5450
|
}
|
|
5417
5451
|
function generatePython(ctx, packageScope) {
|
|
5418
5452
|
const cb = new CodeBuilder(" ");
|
|
5419
5453
|
const scope = packageScope ?? new Scope(PY_RESERVED);
|
|
5420
|
-
const { names, rootType, rootIsStruct, namedTypes, typeDecls, rootTypeTag, paramsType, sigEntries } = buildEmitModel(ctx, scope);
|
|
5454
|
+
const { names, rootType, rootIsStruct, namedTypes, typeDecls, rootTypeTag, paramsType, sigEntries, nestedFactories } = buildEmitModel(ctx, scope);
|
|
5421
5455
|
cb.comment("This file was auto generated by Styx.", "# ");
|
|
5422
5456
|
cb.comment("Do not edit this file directly.", "# ");
|
|
5423
5457
|
cb.blank();
|
|
@@ -5436,6 +5470,10 @@ function generatePython(ctx, packageScope) {
|
|
|
5436
5470
|
emitParamsFactory$1(sigEntries, names.paramsFn, paramsType, rootTypeTag, cb);
|
|
5437
5471
|
cb.blank();
|
|
5438
5472
|
}
|
|
5473
|
+
for (const nf of nestedFactories) {
|
|
5474
|
+
emitParamsFactory$1(nf.sigEntries, nf.funcName, nf.typeName, nf.typeTag, cb);
|
|
5475
|
+
cb.blank();
|
|
5476
|
+
}
|
|
5439
5477
|
emitValidate$1(ctx, rootType, ctx.expr, paramsType, names.validate, resolveTypeName(namedTypes), scope, cb);
|
|
5440
5478
|
cb.blank();
|
|
5441
5479
|
emitBuildCargs$1(ctx, rootType, paramsType, names.cargs, cb);
|
|
@@ -5455,6 +5493,7 @@ function generatePython(ctx, packageScope) {
|
|
|
5455
5493
|
names.cargs,
|
|
5456
5494
|
...[names.outputsFn],
|
|
5457
5495
|
...rootIsStruct ? [names.paramsFn, names.execute] : [],
|
|
5496
|
+
...nestedFactories.map((nf) => nf.funcName),
|
|
5458
5497
|
names.validate,
|
|
5459
5498
|
names.wrapper
|
|
5460
5499
|
];
|
|
@@ -5622,9 +5661,13 @@ function structAtType(type) {
|
|
|
5622
5661
|
}
|
|
5623
5662
|
/**
|
|
5624
5663
|
* Render a struct config as a host object literal (Python dict / TS object).
|
|
5625
|
-
* Keys are the Boutiques wire names (the generated TypedDict / interface keys)
|
|
5626
|
-
*
|
|
5627
|
-
*
|
|
5664
|
+
* Keys are the Boutiques wire names (the generated TypedDict / interface keys).
|
|
5665
|
+
*
|
|
5666
|
+
* When the dialect supplies `structConstructor` (Python, which generates a
|
|
5667
|
+
* per-struct `_params` builder), the struct is rendered as that factory call
|
|
5668
|
+
* instead - e.g. `tool_corrected_output_params(file="...")`. Backends without
|
|
5669
|
+
* per-struct builders (TypeScript) leave the hook unset and build the plain
|
|
5670
|
+
* object literal below.
|
|
5628
5671
|
*
|
|
5629
5672
|
* `@type` is emitted from the struct's literal discriminator field when present
|
|
5630
5673
|
* (union variants carry a required, load-bearing `@type`); for the root call the
|
|
@@ -5634,6 +5677,10 @@ function structAtType(type) {
|
|
|
5634
5677
|
*/
|
|
5635
5678
|
function renderStructLiteral(value, type, indent, d, injectAtType) {
|
|
5636
5679
|
const obj = isRecord(value) ? value : {};
|
|
5680
|
+
if (injectAtType === void 0 && d.structConstructor) {
|
|
5681
|
+
const call = d.structConstructor(type, obj, indent, d);
|
|
5682
|
+
if (call !== void 0) return call;
|
|
5683
|
+
}
|
|
5637
5684
|
const inner = indent + d.indentUnit;
|
|
5638
5685
|
const lines = [];
|
|
5639
5686
|
const atType = structAtType(type) ?? injectAtType;
|
|
@@ -5709,6 +5756,41 @@ const pyDialect = {
|
|
|
5709
5756
|
objKey: (k) => pyStr(k)
|
|
5710
5757
|
};
|
|
5711
5758
|
/**
|
|
5759
|
+
* Build a Python dialect that renders nested structs as `_params` factory calls
|
|
5760
|
+
* (matching the generated code's per-struct builders) rather than plain dicts.
|
|
5761
|
+
* Each factory is looked up by the struct's structural key; field values use the
|
|
5762
|
+
* factory's scrubbed host kwarg names (from the same `buildSigEntries` the
|
|
5763
|
+
* generated factory is built from), and the function name is package-qualified
|
|
5764
|
+
* so the call resolves through `from <root> import <pkg>`. A struct with no
|
|
5765
|
+
* registered factory (shouldn't happen for well-formed nested types) falls back
|
|
5766
|
+
* to the plain dict literal.
|
|
5767
|
+
*/
|
|
5768
|
+
function pyDialectWithFactories(model, pkg) {
|
|
5769
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
5770
|
+
for (const f of model.nestedFactories) byKey.set(f.structKey, {
|
|
5771
|
+
call: pkg ? `${pkg}.${f.funcName}` : f.funcName,
|
|
5772
|
+
nameByWire: new Map(f.sigEntries.map((e) => [e.wireKey, e.name]))
|
|
5773
|
+
});
|
|
5774
|
+
return {
|
|
5775
|
+
...pyDialect,
|
|
5776
|
+
structConstructor(type, value, indent, d) {
|
|
5777
|
+
const entry = byKey.get(structKey(type));
|
|
5778
|
+
if (!entry) return void 0;
|
|
5779
|
+
const inner = indent + d.indentUnit;
|
|
5780
|
+
const lines = [];
|
|
5781
|
+
for (const [wireKey, fieldType] of Object.entries(type.fields)) {
|
|
5782
|
+
if (wireKey === "@type") continue;
|
|
5783
|
+
if (fieldType.kind === "literal") continue;
|
|
5784
|
+
if (!(wireKey in value)) continue;
|
|
5785
|
+
const name = entry.nameByWire.get(wireKey) ?? wireKey;
|
|
5786
|
+
lines.push(`${inner}${name}=${renderValue(value[wireKey], fieldType, inner, d)},`);
|
|
5787
|
+
}
|
|
5788
|
+
if (lines.length === 0) return `${entry.call}()`;
|
|
5789
|
+
return `${entry.call}(\n${lines.join("\n")}\n${indent})`;
|
|
5790
|
+
}
|
|
5791
|
+
};
|
|
5792
|
+
}
|
|
5793
|
+
/**
|
|
5712
5794
|
* Render a Python call snippet for one tool from a config object (the params
|
|
5713
5795
|
* dict the form produces, keyed by Boutiques wire names).
|
|
5714
5796
|
*
|
|
@@ -5717,8 +5799,9 @@ const pyDialect = {
|
|
|
5717
5799
|
* *scrubbed host* identifiers (`float` -> `float_`), not the wire keys; the
|
|
5718
5800
|
* per-field mapping comes from the same `buildSigEntries` the generated wrapper
|
|
5719
5801
|
* is built from, so the snippet matches the real signature. Nested structs /
|
|
5720
|
-
* union variants / lists-of-structs
|
|
5721
|
-
*
|
|
5802
|
+
* union variants / lists-of-structs render as calls to their generated
|
|
5803
|
+
* `_params` factory (`fsl.bet_x_params(...)`), matching the per-struct builders
|
|
5804
|
+
* the Python backend emits.
|
|
5722
5805
|
*
|
|
5723
5806
|
* Union- (or otherwise non-struct-) rooted tools have no kwarg wrapper; the
|
|
5724
5807
|
* single dict-style `<tool>` entry is called with one object-literal argument.
|
|
@@ -5739,21 +5822,22 @@ function renderPythonCall(ctx, config, opts = {}) {
|
|
|
5739
5822
|
const model = buildEmitModel(ctx);
|
|
5740
5823
|
const pkg = ctx.package?.name;
|
|
5741
5824
|
const callee = pkg ? `${pkg}.${model.names.wrapper}` : model.names.wrapper;
|
|
5742
|
-
const
|
|
5825
|
+
const dialect = pyDialectWithFactories(model, pkg);
|
|
5826
|
+
const call = model.rootIsStruct && model.rootType.kind === "struct" ? renderKwargCall(callee, model.sigEntries, model.rootType, config, dialect) : `${callee}(${renderValue(config, model.rootType, "", dialect)})`;
|
|
5743
5827
|
if (opts.includeImport === false || !pkg) return call;
|
|
5744
5828
|
const root = opts.packageRoot ?? ctx.project?.name;
|
|
5745
5829
|
return `${root ? `from ${root} import ${pkg}` : `import ${pkg}`}\n\n${call}`;
|
|
5746
5830
|
}
|
|
5747
5831
|
/** Render `callee(name=value, ...)` for a struct root using scrubbed kwarg names. */
|
|
5748
|
-
function renderKwargCall(callee, sigEntries, rootType, config) {
|
|
5832
|
+
function renderKwargCall(callee, sigEntries, rootType, config, dialect) {
|
|
5749
5833
|
const nameFor = new Map(sigEntries.map((e) => [e.wireKey, e.name]));
|
|
5750
|
-
const indent =
|
|
5834
|
+
const indent = dialect.indentUnit;
|
|
5751
5835
|
const lines = [];
|
|
5752
5836
|
for (const [wireKey, fieldType] of Object.entries(rootType.fields)) {
|
|
5753
5837
|
if (fieldType.kind === "literal") continue;
|
|
5754
5838
|
if (!(wireKey in config)) continue;
|
|
5755
5839
|
const name = nameFor.get(wireKey) ?? wireKey;
|
|
5756
|
-
lines.push(`${indent}${name}=${renderValue(config[wireKey], fieldType, indent,
|
|
5840
|
+
lines.push(`${indent}${name}=${renderValue(config[wireKey], fieldType, indent, dialect)},`);
|
|
5757
5841
|
}
|
|
5758
5842
|
if (lines.length === 0) return `${callee}()`;
|
|
5759
5843
|
return `${callee}(\n${lines.join("\n")}\n)`;
|