@styx-api/core 0.5.3 → 0.6.0
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 +84 -11
- 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 +84 -11
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- 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/dist/index.mjs
CHANGED
|
@@ -5400,7 +5400,29 @@ function buildEmitModel(ctx, scope = new Scope(PY_RESERVED)) {
|
|
|
5400
5400
|
names.params = (rootType.kind === "struct" ? namedTypes.get(structKey(rootType)) : void 0) ?? (rootType.kind === "union" ? namedTypes.get(unionKey(rootType)) : void 0) ?? names.params;
|
|
5401
5401
|
const rootTypeTag = appId && pkg ? `${pkg}/${appId}` : void 0;
|
|
5402
5402
|
const paramsType = rootType.kind === "struct" || rootType.kind === "union" ? names.params : mapType$1(rootType, resolveTypeName(namedTypes));
|
|
5403
|
+
const hostName = (childScope, wireKey) => childScope.add(pyScrubIdent(snakeCase(wireKey), PY_RESERVED));
|
|
5404
|
+
const resolve = resolveTypeName(namedTypes);
|
|
5403
5405
|
const sigScope = scope.child(["params", "runner"]);
|
|
5406
|
+
const sigEntries = rootIsStruct && rootType.kind === "struct" ? buildSigEntries(rootType, collectFieldInfo(ctx, rootType), (wireKey) => hostName(sigScope, wireKey), pySigOptions(resolve)) : [];
|
|
5407
|
+
const rootStructKey = rootIsStruct && rootType.kind === "struct" ? structKey(rootType) : void 0;
|
|
5408
|
+
const nestedFactories = [];
|
|
5409
|
+
for (const decl of typeDecls) {
|
|
5410
|
+
if (decl.type.kind !== "struct") continue;
|
|
5411
|
+
const sKey = structKey(decl.type);
|
|
5412
|
+
if (sKey === rootStructKey) continue;
|
|
5413
|
+
const funcName = scope.add(pyScrubIdent(snakeCase(decl.name) + "_params", PY_RESERVED));
|
|
5414
|
+
const atType = decl.type.fields["@type"];
|
|
5415
|
+
const typeTag = atType && atType.kind === "literal" && typeof atType.value === "string" ? atType.value : void 0;
|
|
5416
|
+
const factoryScope = scope.child(["params"]);
|
|
5417
|
+
const factorySig = buildSigEntries(decl.type, collectFieldInfo(ctx, decl.type), (wireKey) => hostName(factoryScope, wireKey), pySigOptions(resolve));
|
|
5418
|
+
nestedFactories.push({
|
|
5419
|
+
typeName: decl.name,
|
|
5420
|
+
structKey: sKey,
|
|
5421
|
+
funcName,
|
|
5422
|
+
typeTag,
|
|
5423
|
+
sigEntries: factorySig
|
|
5424
|
+
});
|
|
5425
|
+
}
|
|
5404
5426
|
return {
|
|
5405
5427
|
appId,
|
|
5406
5428
|
pkg,
|
|
@@ -5411,13 +5433,14 @@ function buildEmitModel(ctx, scope = new Scope(PY_RESERVED)) {
|
|
|
5411
5433
|
typeDecls,
|
|
5412
5434
|
rootTypeTag,
|
|
5413
5435
|
paramsType,
|
|
5414
|
-
sigEntries
|
|
5436
|
+
sigEntries,
|
|
5437
|
+
nestedFactories
|
|
5415
5438
|
};
|
|
5416
5439
|
}
|
|
5417
5440
|
function generatePython(ctx, packageScope) {
|
|
5418
5441
|
const cb = new CodeBuilder(" ");
|
|
5419
5442
|
const scope = packageScope ?? new Scope(PY_RESERVED);
|
|
5420
|
-
const { names, rootType, rootIsStruct, namedTypes, typeDecls, rootTypeTag, paramsType, sigEntries } = buildEmitModel(ctx, scope);
|
|
5443
|
+
const { names, rootType, rootIsStruct, namedTypes, typeDecls, rootTypeTag, paramsType, sigEntries, nestedFactories } = buildEmitModel(ctx, scope);
|
|
5421
5444
|
cb.comment("This file was auto generated by Styx.", "# ");
|
|
5422
5445
|
cb.comment("Do not edit this file directly.", "# ");
|
|
5423
5446
|
cb.blank();
|
|
@@ -5436,6 +5459,10 @@ function generatePython(ctx, packageScope) {
|
|
|
5436
5459
|
emitParamsFactory$1(sigEntries, names.paramsFn, paramsType, rootTypeTag, cb);
|
|
5437
5460
|
cb.blank();
|
|
5438
5461
|
}
|
|
5462
|
+
for (const nf of nestedFactories) {
|
|
5463
|
+
emitParamsFactory$1(nf.sigEntries, nf.funcName, nf.typeName, nf.typeTag, cb);
|
|
5464
|
+
cb.blank();
|
|
5465
|
+
}
|
|
5439
5466
|
emitValidate$1(ctx, rootType, ctx.expr, paramsType, names.validate, resolveTypeName(namedTypes), scope, cb);
|
|
5440
5467
|
cb.blank();
|
|
5441
5468
|
emitBuildCargs$1(ctx, rootType, paramsType, names.cargs, cb);
|
|
@@ -5455,6 +5482,7 @@ function generatePython(ctx, packageScope) {
|
|
|
5455
5482
|
names.cargs,
|
|
5456
5483
|
...[names.outputsFn],
|
|
5457
5484
|
...rootIsStruct ? [names.paramsFn, names.execute] : [],
|
|
5485
|
+
...nestedFactories.map((nf) => nf.funcName),
|
|
5458
5486
|
names.validate,
|
|
5459
5487
|
names.wrapper
|
|
5460
5488
|
];
|
|
@@ -5622,9 +5650,13 @@ function structAtType(type) {
|
|
|
5622
5650
|
}
|
|
5623
5651
|
/**
|
|
5624
5652
|
* 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
|
-
*
|
|
5653
|
+
* Keys are the Boutiques wire names (the generated TypedDict / interface keys).
|
|
5654
|
+
*
|
|
5655
|
+
* When the dialect supplies `structConstructor` (Python, which generates a
|
|
5656
|
+
* per-struct `_params` builder), the struct is rendered as that factory call
|
|
5657
|
+
* instead - e.g. `tool_corrected_output_params(file="...")`. Backends without
|
|
5658
|
+
* per-struct builders (TypeScript) leave the hook unset and build the plain
|
|
5659
|
+
* object literal below.
|
|
5628
5660
|
*
|
|
5629
5661
|
* `@type` is emitted from the struct's literal discriminator field when present
|
|
5630
5662
|
* (union variants carry a required, load-bearing `@type`); for the root call the
|
|
@@ -5634,6 +5666,10 @@ function structAtType(type) {
|
|
|
5634
5666
|
*/
|
|
5635
5667
|
function renderStructLiteral(value, type, indent, d, injectAtType) {
|
|
5636
5668
|
const obj = isRecord(value) ? value : {};
|
|
5669
|
+
if (injectAtType === void 0 && d.structConstructor) {
|
|
5670
|
+
const call = d.structConstructor(type, obj, indent, d);
|
|
5671
|
+
if (call !== void 0) return call;
|
|
5672
|
+
}
|
|
5637
5673
|
const inner = indent + d.indentUnit;
|
|
5638
5674
|
const lines = [];
|
|
5639
5675
|
const atType = structAtType(type) ?? injectAtType;
|
|
@@ -5709,6 +5745,41 @@ const pyDialect = {
|
|
|
5709
5745
|
objKey: (k) => pyStr(k)
|
|
5710
5746
|
};
|
|
5711
5747
|
/**
|
|
5748
|
+
* Build a Python dialect that renders nested structs as `_params` factory calls
|
|
5749
|
+
* (matching the generated code's per-struct builders) rather than plain dicts.
|
|
5750
|
+
* Each factory is looked up by the struct's structural key; field values use the
|
|
5751
|
+
* factory's scrubbed host kwarg names (from the same `buildSigEntries` the
|
|
5752
|
+
* generated factory is built from), and the function name is package-qualified
|
|
5753
|
+
* so the call resolves through `from <root> import <pkg>`. A struct with no
|
|
5754
|
+
* registered factory (shouldn't happen for well-formed nested types) falls back
|
|
5755
|
+
* to the plain dict literal.
|
|
5756
|
+
*/
|
|
5757
|
+
function pyDialectWithFactories(model, pkg) {
|
|
5758
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
5759
|
+
for (const f of model.nestedFactories) byKey.set(f.structKey, {
|
|
5760
|
+
call: pkg ? `${pkg}.${f.funcName}` : f.funcName,
|
|
5761
|
+
nameByWire: new Map(f.sigEntries.map((e) => [e.wireKey, e.name]))
|
|
5762
|
+
});
|
|
5763
|
+
return {
|
|
5764
|
+
...pyDialect,
|
|
5765
|
+
structConstructor(type, value, indent, d) {
|
|
5766
|
+
const entry = byKey.get(structKey(type));
|
|
5767
|
+
if (!entry) return void 0;
|
|
5768
|
+
const inner = indent + d.indentUnit;
|
|
5769
|
+
const lines = [];
|
|
5770
|
+
for (const [wireKey, fieldType] of Object.entries(type.fields)) {
|
|
5771
|
+
if (wireKey === "@type") continue;
|
|
5772
|
+
if (fieldType.kind === "literal") continue;
|
|
5773
|
+
if (!(wireKey in value)) continue;
|
|
5774
|
+
const name = entry.nameByWire.get(wireKey) ?? wireKey;
|
|
5775
|
+
lines.push(`${inner}${name}=${renderValue(value[wireKey], fieldType, inner, d)},`);
|
|
5776
|
+
}
|
|
5777
|
+
if (lines.length === 0) return `${entry.call}()`;
|
|
5778
|
+
return `${entry.call}(\n${lines.join("\n")}\n${indent})`;
|
|
5779
|
+
}
|
|
5780
|
+
};
|
|
5781
|
+
}
|
|
5782
|
+
/**
|
|
5712
5783
|
* Render a Python call snippet for one tool from a config object (the params
|
|
5713
5784
|
* dict the form produces, keyed by Boutiques wire names).
|
|
5714
5785
|
*
|
|
@@ -5717,8 +5788,9 @@ const pyDialect = {
|
|
|
5717
5788
|
* *scrubbed host* identifiers (`float` -> `float_`), not the wire keys; the
|
|
5718
5789
|
* per-field mapping comes from the same `buildSigEntries` the generated wrapper
|
|
5719
5790
|
* is built from, so the snippet matches the real signature. Nested structs /
|
|
5720
|
-
* union variants / lists-of-structs
|
|
5721
|
-
*
|
|
5791
|
+
* union variants / lists-of-structs render as calls to their generated
|
|
5792
|
+
* `_params` factory (`fsl.bet_x_params(...)`), matching the per-struct builders
|
|
5793
|
+
* the Python backend emits.
|
|
5722
5794
|
*
|
|
5723
5795
|
* Union- (or otherwise non-struct-) rooted tools have no kwarg wrapper; the
|
|
5724
5796
|
* single dict-style `<tool>` entry is called with one object-literal argument.
|
|
@@ -5739,21 +5811,22 @@ function renderPythonCall(ctx, config, opts = {}) {
|
|
|
5739
5811
|
const model = buildEmitModel(ctx);
|
|
5740
5812
|
const pkg = ctx.package?.name;
|
|
5741
5813
|
const callee = pkg ? `${pkg}.${model.names.wrapper}` : model.names.wrapper;
|
|
5742
|
-
const
|
|
5814
|
+
const dialect = pyDialectWithFactories(model, pkg);
|
|
5815
|
+
const call = model.rootIsStruct && model.rootType.kind === "struct" ? renderKwargCall(callee, model.sigEntries, model.rootType, config, dialect) : `${callee}(${renderValue(config, model.rootType, "", dialect)})`;
|
|
5743
5816
|
if (opts.includeImport === false || !pkg) return call;
|
|
5744
5817
|
const root = opts.packageRoot ?? ctx.project?.name;
|
|
5745
5818
|
return `${root ? `from ${root} import ${pkg}` : `import ${pkg}`}\n\n${call}`;
|
|
5746
5819
|
}
|
|
5747
5820
|
/** Render `callee(name=value, ...)` for a struct root using scrubbed kwarg names. */
|
|
5748
|
-
function renderKwargCall(callee, sigEntries, rootType, config) {
|
|
5821
|
+
function renderKwargCall(callee, sigEntries, rootType, config, dialect) {
|
|
5749
5822
|
const nameFor = new Map(sigEntries.map((e) => [e.wireKey, e.name]));
|
|
5750
|
-
const indent =
|
|
5823
|
+
const indent = dialect.indentUnit;
|
|
5751
5824
|
const lines = [];
|
|
5752
5825
|
for (const [wireKey, fieldType] of Object.entries(rootType.fields)) {
|
|
5753
5826
|
if (fieldType.kind === "literal") continue;
|
|
5754
5827
|
if (!(wireKey in config)) continue;
|
|
5755
5828
|
const name = nameFor.get(wireKey) ?? wireKey;
|
|
5756
|
-
lines.push(`${indent}${name}=${renderValue(config[wireKey], fieldType, indent,
|
|
5829
|
+
lines.push(`${indent}${name}=${renderValue(config[wireKey], fieldType, indent, dialect)},`);
|
|
5757
5830
|
}
|
|
5758
5831
|
if (lines.length === 0) return `${callee}()`;
|
|
5759
5832
|
return `${callee}(\n${lines.join("\n")}\n)`;
|