@ifc-lite/flow-nodes 0.6.0 → 0.9.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/README.md +118 -0
- package/dist/ai-classify.d.ts +8 -0
- package/dist/ai-classify.d.ts.map +1 -0
- package/dist/ai-classify.js +113 -0
- package/dist/ai-classify.js.map +1 -0
- package/dist/ai-extract.d.ts +26 -0
- package/dist/ai-extract.d.ts.map +1 -0
- package/dist/ai-extract.js +127 -0
- package/dist/ai-extract.js.map +1 -0
- package/dist/ai-propose-fields.d.ts +12 -0
- package/dist/ai-propose-fields.d.ts.map +1 -0
- package/dist/ai-propose-fields.js +57 -0
- package/dist/ai-propose-fields.js.map +1 -0
- package/dist/ai-propose.d.ts +3 -0
- package/dist/ai-propose.d.ts.map +1 -0
- package/dist/ai-propose.js +94 -0
- package/dist/ai-propose.js.map +1 -0
- package/dist/ai-response-schemas.d.ts +11 -0
- package/dist/ai-response-schemas.d.ts.map +1 -0
- package/dist/ai-response-schemas.js +46 -0
- package/dist/ai-response-schemas.js.map +1 -0
- package/dist/ai-service.d.ts +60 -0
- package/dist/ai-service.d.ts.map +1 -0
- package/dist/ai-service.js +109 -0
- package/dist/ai-service.js.map +1 -0
- package/dist/ai-summarize.d.ts +3 -0
- package/dist/ai-summarize.d.ts.map +1 -0
- package/dist/ai-summarize.js +75 -0
- package/dist/ai-summarize.js.map +1 -0
- package/dist/ai.d.ts +15 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +10 -0
- package/dist/ai.js.map +1 -0
- package/dist/bcf-nodes.d.ts.map +1 -1
- package/dist/bcf-nodes.js +12 -2
- package/dist/bcf-nodes.js.map +1 -1
- package/dist/bcf-publication-capacity.d.ts +5 -0
- package/dist/bcf-publication-capacity.d.ts.map +1 -0
- package/dist/bcf-publication-capacity.js +44 -0
- package/dist/bcf-publication-capacity.js.map +1 -0
- package/dist/bcf-publication-check.d.ts +4 -0
- package/dist/bcf-publication-check.d.ts.map +1 -0
- package/dist/bcf-publication-check.js +100 -0
- package/dist/bcf-publication-check.js.map +1 -0
- package/dist/bcf-publication-codec.d.ts +53 -0
- package/dist/bcf-publication-codec.d.ts.map +1 -0
- package/dist/bcf-publication-codec.js +247 -0
- package/dist/bcf-publication-codec.js.map +1 -0
- package/dist/bcf-publication-epochs.d.ts +17 -0
- package/dist/bcf-publication-epochs.d.ts.map +1 -0
- package/dist/bcf-publication-epochs.js +104 -0
- package/dist/bcf-publication-epochs.js.map +1 -0
- package/dist/bcf-publication-history.d.ts +8 -0
- package/dist/bcf-publication-history.d.ts.map +1 -0
- package/dist/bcf-publication-history.js +84 -0
- package/dist/bcf-publication-history.js.map +1 -0
- package/dist/bcf-publication-node.d.ts +4 -0
- package/dist/bcf-publication-node.d.ts.map +1 -0
- package/dist/bcf-publication-node.js +235 -0
- package/dist/bcf-publication-node.js.map +1 -0
- package/dist/bcf-publication-owner.d.ts +7 -0
- package/dist/bcf-publication-owner.d.ts.map +1 -0
- package/dist/bcf-publication-owner.js +94 -0
- package/dist/bcf-publication-owner.js.map +1 -0
- package/dist/bcf-publication-store.d.ts +40 -0
- package/dist/bcf-publication-store.d.ts.map +1 -0
- package/dist/bcf-publication-store.js +312 -0
- package/dist/bcf-publication-store.js.map +1 -0
- package/dist/bcf-publication-types.d.ts +105 -0
- package/dist/bcf-publication-types.d.ts.map +1 -0
- package/dist/bcf-publication-types.js +5 -0
- package/dist/bcf-publication-types.js.map +1 -0
- package/dist/bcf-publication.d.ts +13 -0
- package/dist/bcf-publication.d.ts.map +1 -0
- package/dist/bcf-publication.js +24 -0
- package/dist/bcf-publication.js.map +1 -0
- package/dist/bcf-write-gateway.d.ts +41 -0
- package/dist/bcf-write-gateway.d.ts.map +1 -0
- package/dist/bcf-write-gateway.js +75 -0
- package/dist/bcf-write-gateway.js.map +1 -0
- package/dist/capability.d.ts +4 -0
- package/dist/capability.d.ts.map +1 -0
- package/dist/capability.js +21 -0
- package/dist/capability.js.map +1 -0
- package/dist/element-nodes.d.ts +9 -1
- package/dist/element-nodes.d.ts.map +1 -1
- package/dist/element-nodes.js +82 -9
- package/dist/element-nodes.js.map +1 -1
- package/dist/host.d.ts +41 -40
- package/dist/host.d.ts.map +1 -1
- package/dist/host.js +2 -31
- package/dist/host.js.map +1 -1
- package/dist/ids-nodes.d.ts +23 -0
- package/dist/ids-nodes.d.ts.map +1 -0
- package/dist/ids-nodes.js +180 -0
- package/dist/ids-nodes.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/ports.d.ts +38 -0
- package/dist/ports.d.ts.map +1 -0
- package/dist/ports.js +14 -0
- package/dist/ports.js.map +1 -0
- package/dist/review-node.d.ts +3 -0
- package/dist/review-node.d.ts.map +1 -0
- package/dist/review-node.js +20 -0
- package/dist/review-node.js.map +1 -0
- package/dist/speckle/url.d.ts.map +1 -1
- package/dist/speckle/url.js +9 -1
- package/dist/speckle/url.js.map +1 -1
- package/package.json +32 -12
package/README.md
CHANGED
|
@@ -31,6 +31,18 @@ const result = await runFlow(doc, { host: { bim }, registry, features: BROWSER_F
|
|
|
31
31
|
`host.grants` (parsed capabilities from `@ifc-lite/extensions`) gates every node;
|
|
32
32
|
omit it only for a trusted caller such as the CLI running a local file.
|
|
33
33
|
|
|
34
|
+
## Stair and railing authoring
|
|
35
|
+
|
|
36
|
+
`element.stair` and `element.railing` produce specs for the existing tracked
|
|
37
|
+
`model.addElement` node. Their PascalCase parameters reuse the canonical SDK
|
|
38
|
+
builders; dimensions are storey-local metres. Stair update/removal removes the
|
|
39
|
+
uniquely owned parent/flight pair, retaining shared representation leaves and
|
|
40
|
+
refusing ambiguous ownership or foreign product references. Unsupported backend
|
|
41
|
+
capabilities refuse explicitly. All `model.addElement` updates use the optional
|
|
42
|
+
atomic replacement capability, preserving the old graph and tracking entry
|
|
43
|
+
on refusal, including a spec-kind change. Reuse a tracking store for keep/update/remove;
|
|
44
|
+
public MCP `run_flow` starts fresh tracking on each call.
|
|
45
|
+
|
|
34
46
|
## Session automation host
|
|
35
47
|
|
|
36
48
|
The seven standard automation definitions call `host.automation` and pass the
|
|
@@ -63,3 +75,109 @@ not rerun a comparison against the loaded models.
|
|
|
63
75
|
## License
|
|
64
76
|
|
|
65
77
|
MPL-2.0
|
|
78
|
+
|
|
79
|
+
The separate `@ifc-lite/flow-nodes/ai` entry includes `ai.propose`: a bounded,
|
|
80
|
+
review-required draft over selected finding columns and explicit native
|
|
81
|
+
field/value constraints. Its portable `model.changes` output cites captured
|
|
82
|
+
rows and expected values, and performs no writes. Current-state validation and
|
|
83
|
+
mutation approval remain native host responsibilities. See the
|
|
84
|
+
[AI node and checkpoint contract](../../docs/guide/flow.md#ai-nodes-and-review-checkpoints).
|
|
85
|
+
|
|
86
|
+
## BCF write response boundary
|
|
87
|
+
|
|
88
|
+
Both create operations enrol the actual resolved bearer, including short literal
|
|
89
|
+
and Player values. Successful DTO data, dynamic keys and outward diagnostics
|
|
90
|
+
redact raw and JSON-escaped bearer occurrences. The primary `guid` and optional
|
|
91
|
+
comment `topic_guid`, `viewpoint_guid` and `reply_to_comment_guid` are validated
|
|
92
|
+
before an owned gateway saves the receipt and before any returned receipt is
|
|
93
|
+
emitted. A credential-bearing identifier leaves the retained server effect
|
|
94
|
+
uncertain; it is never silently renamed or retried. Clean identifier values,
|
|
95
|
+
nullable optional relationships and their fixed protocol keys stay exact, even
|
|
96
|
+
when a short bearer equals part of a key. Browser intent admission validates a
|
|
97
|
+
closed schema and checks credential values and dynamic keys, so a bearer such
|
|
98
|
+
as `id` does not reject fixed names such as `topicGuid`. Unknown fields and
|
|
99
|
+
credential values, URL credentials and Authorization/Bearer syntax stay refused.
|
|
100
|
+
A fixed protocol field name is not server-echoed credential data. Gateway errors preserve the host's refusal
|
|
101
|
+
or recovery message as a plain `Error`, without its original class or cause.
|
|
102
|
+
Direct sends use dispatch provenance: a preflight denial is definite; a refused
|
|
103
|
+
redirect, HTTP 409, HTTP 5xx or missing response remains uncertain and never retries.
|
|
104
|
+
Direct remote failures expose only the safe failure class and valid HTTP status;
|
|
105
|
+
remote response bodies, detail URLs and error causes are discarded. Known local
|
|
106
|
+
preflight diagnostics and gateway refusal/recovery text are retained after
|
|
107
|
+
redacting the actual bearer, without carrying their original error cause.
|
|
108
|
+
|
|
109
|
+
`review.snapshot` is a volatile `model.read` node over a host-provided native
|
|
110
|
+
Review service. Its optional `after` input sequences the read after an analysis;
|
|
111
|
+
its bounded `snapshot` contains actual facts, coverage and producer provenance.
|
|
112
|
+
Hosts without a native capture service report the missing
|
|
113
|
+
`review` backend capability explicitly. This node does not rerun analysis or
|
|
114
|
+
write models, artifacts, publications or decisions. See
|
|
115
|
+
[the native Review read contract](../../docs/guide/flow.md#reading-captured-native-review).
|
|
116
|
+
|
|
117
|
+
## Durable native BCF publication
|
|
118
|
+
|
|
119
|
+
The Node-only `@ifc-lite/flow-nodes/bcf-publication-node` entry exports
|
|
120
|
+
`createNativeBcfWriteGateway({ directory, namespace, secrets })` for
|
|
121
|
+
`FlowHost.bcfWrites`. CLI hosts supply it automatically. An immutable,
|
|
122
|
+
fsynced intent is claimed before a create; a lost response or interrupted host
|
|
123
|
+
blocks the same target and payload across restarts. Bearers are ephemeral;
|
|
124
|
+
a payload containing a known run or write token is refused before persistence.
|
|
125
|
+
Known credentials are checked in values and dynamic keys; fixed public schema
|
|
126
|
+
keys such as `id`, `topicGuid` and `remoteGuid` are protocol names, not credential
|
|
127
|
+
data. Strict record and payload decoders still reject those names in an unknown
|
|
128
|
+
location. Authorization/token keys and credential URL/Bearer syntax remain
|
|
129
|
+
refused, and credential-bearing identifier values remain unsafe.
|
|
130
|
+
Every generated journal record is checked against the current known credentials
|
|
131
|
+
before publication. A credential that cannot be represented without appearing in
|
|
132
|
+
fixed metadata, such as the literal bearer `done`, is refused before the POST.
|
|
133
|
+
Fixed schema keys remain valid: bearers `id` and `intent` can use clean payloads
|
|
134
|
+
and receipt values. A later generated-value collision, such as a receipt timestamp
|
|
135
|
+
matching a known credential, refuses success and leaves the saved intent unresolved
|
|
136
|
+
for explicit checked recovery; it never persists that credential or retries.
|
|
137
|
+
BCF write nodes also redact the actual resolved bearer from successful server
|
|
138
|
+
responses before exposing output ports, including literal and Player tokens.
|
|
139
|
+
|
|
140
|
+
`list()` inspects one bounded local page. `listPage({cursor, limit})` returns
|
|
141
|
+
`{entries,nextCursor?}`; follow every cursor, including empty progress pages. `check(id, { token, networkGrants })` reads the
|
|
142
|
+
original HTTPS target with fresh capability grants and records matching GUID
|
|
143
|
+
candidates, including trim-equivalent title, description and comment text.
|
|
144
|
+
Server-edited or omitted fields cannot prove absence: unmatched objects make
|
|
145
|
+
the check unavailable. Only a complete empty enumeration with `confirmFullReadAccess: true` proves absence; the operator asserts complete visibility in the same access scope as the original write. Token presence alone is not proof of visibility. Without the assertion an empty enumeration is unavailable, while matching candidates remain available;
|
|
146
|
+
the create protocol supplies no stable client marker for excluding other objects.
|
|
147
|
+
It never retries a write. `reconcile(id, guid)` explicitly selects
|
|
148
|
+
a checked candidate. `confirmAbsent(id)` explicitly releases an intent only
|
|
149
|
+
after the latest complete check proved absence and persisted `fullReadAccessConfirmed: true`. Older markerless absence proofs remain readable but need a fresh asserted check before release. Anonymous genuinely public reads remain supported. No credential or principal identity is persisted. A check alone never
|
|
150
|
+
releases an unresolved create. Preserve the journal when an outcome is unknown.
|
|
151
|
+
|
|
152
|
+
Storage uses a nonempty `IFCLITE_BCF_OUTBOX_DIR` or defaults to `~/.ifc-lite/bcf-outbox`, isolated
|
|
153
|
+
by namespace. Filesystems without exclusive hard links or directory fsync
|
|
154
|
+
refuse publication. Malformed, oversized or orphaned records also refuse;
|
|
155
|
+
records are bounded to 256 KiB, with 256 generations per digest and 16 checks
|
|
156
|
+
per active immutable epoch. Immutable `v2/<prefix>/<digest>`
|
|
157
|
+
storage retains every receipt; 256 buckets each hold at most 4096 never-deleted
|
|
158
|
+
digest slots. Capacity refusal precedes sending and leaves existing recovery
|
|
159
|
+
readable. Listing streams history and bounds each page to 1000 rows, 5 MiB and
|
|
160
|
+
50,000 bounded record-work units; cursors are weakly consistent during concurrent writes. Old flat
|
|
161
|
+
journals are refused: preserve them and recover using their original build.
|
|
162
|
+
Linux boot/starttime and Windows creation-time queries distinguish process
|
|
163
|
+
instances. Unknown owners require `confirmOwnerStopped: true` on `check`, only
|
|
164
|
+
after operator verification; observed live instances always block recovery. The browser
|
|
165
|
+
continues to use its IndexedDB publication controls and the shared pure
|
|
166
|
+
`@ifc-lite/flow-nodes/bcf-publication` error classifier.
|
|
167
|
+
|
|
168
|
+
Recovery check epochs retain prior immutable files and locate the latest marker
|
|
169
|
+
with bounded exponential/binary probes over safe integer identities. Validation
|
|
170
|
+
reads the latest epoch and its embedded preceding proof; archived epoch bodies
|
|
171
|
+
are retained as evidence, without rereading their full history. An interrupted
|
|
172
|
+
check resumes the same reserved check ID with a fresh authorized read. A completed
|
|
173
|
+
unavailable check at an epoch boundary permits the next explicit check in a new
|
|
174
|
+
epoch, competing with settlement on the same exclusive transition. An empty new
|
|
175
|
+
epoch marker preserves the preceding proof. These operations never resend a create.
|
|
176
|
+
|
|
177
|
+
A native process admits at most 64 concurrent or locally completed sends whose
|
|
178
|
+
outcome could not be saved. Completed durable receipts consume no local tracking
|
|
179
|
+
slot. At this bound new creates refuse before any effect; checked reconciliation
|
|
180
|
+
or confirmed absence releases the retained local completion. No completion
|
|
181
|
+
identity is evicted to make room. Reads synchronize the visible receipt or proof's
|
|
182
|
+
directory before acknowledging it; an unresolved directory-sync fault refuses
|
|
183
|
+
recovery and subsequent creates without discarding the existing record.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { FlowNodeDef } from './host.js';
|
|
2
|
+
/** Read one batch reply into per-row results; anything not provable stays `unknown`. */
|
|
3
|
+
export declare function readClassifications(value: Record<string, unknown>, batchKeys: ReadonlySet<string>, labels: ReadonlySet<string>, columns: ReadonlySet<string>): Map<string, {
|
|
4
|
+
label: string | null;
|
|
5
|
+
evidence: string[];
|
|
6
|
+
}>;
|
|
7
|
+
export declare const aiClassifyNode: FlowNodeDef;
|
|
8
|
+
//# sourceMappingURL=ai-classify.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-classify.d.ts","sourceRoot":"","sources":["../src/ai-classify.ts"],"names":[],"mappings":"AAuBA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAsB7C,wFAAwF;AACxF,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,SAAS,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,GACxH,GAAG,CAAC,MAAM,EAAE;IAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,QAAQ,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAa3D;AAED,eAAO,MAAM,cAAc,EAAE,WA2E5B,CAAC"}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { SCALAR_ITEM, TABLE_ITEM } from './ports.js';
|
|
5
|
+
import { AI_CAPABILITY, AI_FEATURE, aiService, dataBlock, positiveInt, requestJson, rowKeys, sentColumns } from './ai-service.js';
|
|
6
|
+
import { classificationSchema } from './ai-response-schemas.js';
|
|
7
|
+
function categoriesOf(value) {
|
|
8
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 50)
|
|
9
|
+
throw new Error('"categories" must list 1 to 50 { label, definition } entries');
|
|
10
|
+
const seen = new Set();
|
|
11
|
+
return value.map((c, i) => {
|
|
12
|
+
const label = typeof c?.label === 'string' ? c.label.trim() : '';
|
|
13
|
+
if (!label || label.length > 80 || label === 'unknown' || seen.has(label))
|
|
14
|
+
throw new Error(`category ${i + 1} needs a unique label other than "unknown"`);
|
|
15
|
+
seen.add(label);
|
|
16
|
+
return { label, definition: typeof c.definition === 'string' ? c.definition.slice(0, 500) : '' };
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
/** Read one batch reply into per-row results; anything not provable stays `unknown`. */
|
|
20
|
+
export function readClassifications(value, batchKeys, labels, columns) {
|
|
21
|
+
const out = new Map();
|
|
22
|
+
const items = Array.isArray(value.items) ? value.items : [];
|
|
23
|
+
for (const raw of items) {
|
|
24
|
+
if (!raw || typeof raw !== 'object')
|
|
25
|
+
continue;
|
|
26
|
+
const item = raw;
|
|
27
|
+
const key = typeof item.key === 'string' ? item.key : null;
|
|
28
|
+
if (key === null || !batchKeys.has(key) || out.has(key))
|
|
29
|
+
continue;
|
|
30
|
+
const evidence = Array.isArray(item.evidence) ? [...new Set(item.evidence.filter((e) => typeof e === 'string' && columns.has(e)))] : [];
|
|
31
|
+
const label = typeof item.label === 'string' && labels.has(item.label) && evidence.length > 0 ? item.label : null;
|
|
32
|
+
out.set(key, { label, evidence: label ? evidence : [] });
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
export const aiClassifyNode = {
|
|
37
|
+
type: 'ai.classify', title: 'AI classify rows', category: 'ai',
|
|
38
|
+
doc: 'Label each row with one of the listed categories using the host AI model. Rows carry an outcome (classified, unknown, failed, not-sent) and the columns cited as evidence. The run pauses for review of the labels.',
|
|
39
|
+
inputs: [{ name: 'table', type: TABLE_ITEM }],
|
|
40
|
+
outputs: [{ name: 'table', type: TABLE_ITEM }, { name: 'coverage', type: SCALAR_ITEM }],
|
|
41
|
+
params: [
|
|
42
|
+
{ name: 'categories', kind: 'json', default: [], doc: '[{ "label": "...", "definition": "..." }]' },
|
|
43
|
+
{ name: 'version', kind: 'string', default: '1', doc: 'Version of the category definitions, kept with the result.' },
|
|
44
|
+
{ name: 'columns', kind: 'json', default: [], doc: 'Columns sent to the model; select at least one explicitly.' },
|
|
45
|
+
{ name: 'maxRows', kind: 'number', default: 200 },
|
|
46
|
+
{ name: 'batchSize', kind: 'number', default: 25 },
|
|
47
|
+
{ name: 'maxOutputTokens', kind: 'number', default: 2000, doc: 'Output ceiling per request.' },
|
|
48
|
+
],
|
|
49
|
+
capabilities: [AI_CAPABILITY], requires: { backend: [AI_FEATURE], network: true }, volatile: true, review: 'required',
|
|
50
|
+
run: async (ctx, inputs, p) => {
|
|
51
|
+
const table = inputs.table;
|
|
52
|
+
const categories = categoriesOf(p.categories);
|
|
53
|
+
const columns = sentColumns(table, Array.isArray(p.columns) ? p.columns.map(String) : []);
|
|
54
|
+
const maxRows = positiveInt(p.maxRows, 'maxRows', 5000);
|
|
55
|
+
const batchSize = positiveInt(p.batchSize, 'batchSize', 100);
|
|
56
|
+
const maxOutputTokens = positiveInt(p.maxOutputTokens, 'maxOutputTokens', 32_000);
|
|
57
|
+
const service = aiService(ctx);
|
|
58
|
+
const keys = rowKeys(table);
|
|
59
|
+
const outcome = keys.map(() => 'not-sent');
|
|
60
|
+
const label = keys.map(() => null);
|
|
61
|
+
const evidence = keys.map(() => []);
|
|
62
|
+
const labels = new Set(categories.map((c) => c.label));
|
|
63
|
+
const task = [
|
|
64
|
+
`Classify each data row into exactly one category (definitions version ${String(p.version)}):`,
|
|
65
|
+
...categories.map((c) => `- ${c.label}: ${c.definition}`),
|
|
66
|
+
'Reply {"items":[{"key":"<row key>","label":"<category label or unknown>","evidence":["<column name>"]}]} with one item per row.',
|
|
67
|
+
'Cite the column names whose values justify the label. Use "unknown" when the data does not decide it.',
|
|
68
|
+
].join('\n');
|
|
69
|
+
let requests = 0;
|
|
70
|
+
const sendable = keys.map((_, i) => i).slice(0, maxRows);
|
|
71
|
+
for (let start = 0; start < sendable.length; start += batchSize) {
|
|
72
|
+
const batch = sendable.slice(start, start + batchSize);
|
|
73
|
+
const reply = await requestJson(ctx, service, task, dataBlock(table, keys, batch, columns), maxOutputTokens, classificationSchema(batch.map(i => keys[i]), [...labels], columns), 'flow.ai.classify.v1');
|
|
74
|
+
if (reply.kind === 'budget') {
|
|
75
|
+
ctx.log('warn', `the AI budget ran out after ${requests} request(s); ${sendable.length - start} row(s) were not sent`);
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
requests += 1;
|
|
79
|
+
if (reply.kind === 'failed' || !Array.isArray(reply.value.items)) {
|
|
80
|
+
ctx.log('warn', `rows ${start + 1}-${start + batch.length}: ${reply.kind === 'failed' ? reply.message : 'the reply has no items array'}`);
|
|
81
|
+
for (const i of batch)
|
|
82
|
+
outcome[i] = 'failed';
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const read = readClassifications(reply.value, new Set(batch.map((i) => keys[i])), labels, new Set(columns));
|
|
86
|
+
for (const i of batch) {
|
|
87
|
+
const r = read.get(keys[i]);
|
|
88
|
+
label[i] = r?.label ?? null;
|
|
89
|
+
evidence[i] = r?.evidence ?? [];
|
|
90
|
+
outcome[i] = r === undefined ? 'failed' : r.label ? 'classified' : 'unknown';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (keys.length > maxRows)
|
|
94
|
+
ctx.log('warn', `${keys.length - maxRows} row(s) beyond maxRows ${maxRows} were not sent`);
|
|
95
|
+
const count = (o) => outcome.filter((x) => x === o).length;
|
|
96
|
+
const result = {
|
|
97
|
+
key: 'key',
|
|
98
|
+
columns: [
|
|
99
|
+
{ name: 'key', type: 'identifier' }, { name: 'label', type: 'label' },
|
|
100
|
+
{ name: 'evidence', type: 'text' }, { name: 'outcome', type: 'enum' },
|
|
101
|
+
],
|
|
102
|
+
rows: keys.map((key, i) => ({ key, label: label[i], evidence: evidence[i].join(', '), outcome: outcome[i] })),
|
|
103
|
+
};
|
|
104
|
+
return {
|
|
105
|
+
table: result,
|
|
106
|
+
coverage: {
|
|
107
|
+
model: service.model, categoriesVersion: String(p.version), rows: keys.length, requests,
|
|
108
|
+
classified: count('classified'), unknown: count('unknown'), failed: count('failed'), notSent: count('not-sent'),
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
},
|
|
112
|
+
};
|
|
113
|
+
//# sourceMappingURL=ai-classify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-classify.js","sourceRoot":"","sources":["../src/ai-classify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ai.extract` (#6923): typed candidate records from supplied text passages.
|
|
3
|
+
*
|
|
4
|
+
* Each record names the passage it came from and quotes a source span. A
|
|
5
|
+
* record is `supported` only when that span occurs verbatim in its passage
|
|
6
|
+
* and every value has its field's type; otherwise it is kept as
|
|
7
|
+
* `unsupported` with the offending values blanked, so a reviewer sees what
|
|
8
|
+
* the model claimed. This is a quote-and-type check, not a semantic check
|
|
9
|
+
* that the quoted text entails each typed value. All records remain candidates
|
|
10
|
+
* awaiting human review. Passages beyond
|
|
11
|
+
* `maxPassages`, or past a budget stop, are reported as not sent. The run
|
|
12
|
+
* pauses here for review.
|
|
13
|
+
*/
|
|
14
|
+
import type { Cell } from '@ifc-lite/flow';
|
|
15
|
+
import type { FlowNodeDef } from './host.js';
|
|
16
|
+
type FieldType = 'string' | 'number' | 'boolean';
|
|
17
|
+
interface Field {
|
|
18
|
+
readonly name: string;
|
|
19
|
+
readonly type: FieldType;
|
|
20
|
+
readonly description: string;
|
|
21
|
+
}
|
|
22
|
+
/** One reply's records, checked against their passages. */
|
|
23
|
+
export declare function readRecords(value: Record<string, unknown>, passages: ReadonlyMap<number, string>, fields: readonly Field[]): Record<string, Cell>[];
|
|
24
|
+
export declare const aiExtractNode: FlowNodeDef;
|
|
25
|
+
export {};
|
|
26
|
+
//# sourceMappingURL=ai-extract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-extract.d.ts","sourceRoot":"","sources":["../src/ai-extract.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAiB,MAAM,gBAAgB,CAAC;AAE1D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAI7C,KAAK,SAAS,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AACjD,UAAU,KAAK;IAAG,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAAE;AAiBjG,2DAA2D;AAC3D,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,SAAS,KAAK,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,CAsBnJ;AAID,eAAO,MAAM,aAAa,EAAE,WAmE3B,CAAC"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
import { SCALAR_ITEM, SCALAR_LIST, TABLE_ITEM } from './ports.js';
|
|
5
|
+
import { AI_CAPABILITY, AI_FEATURE, aiService, positiveInt, requestJson } from './ai-service.js';
|
|
6
|
+
import { extractionSchema } from './ai-response-schemas.js';
|
|
7
|
+
const RESERVED = new Set(['passage', 'span', 'outcome']);
|
|
8
|
+
function fieldsOf(value) {
|
|
9
|
+
if (!Array.isArray(value) || value.length === 0 || value.length > 30)
|
|
10
|
+
throw new Error('"fields" must list 1 to 30 { name, type, description } entries');
|
|
11
|
+
const seen = new Set();
|
|
12
|
+
return value.map((f, i) => {
|
|
13
|
+
const name = typeof f?.name === 'string' ? f.name.trim() : '';
|
|
14
|
+
const type = f?.type;
|
|
15
|
+
if (!/^[A-Za-z][A-Za-z0-9_]{0,63}$/.test(name) || RESERVED.has(name) || seen.has(name))
|
|
16
|
+
throw new Error(`field ${i + 1} needs a unique identifier name other than passage, span or outcome`);
|
|
17
|
+
if (type !== 'string' && type !== 'number' && type !== 'boolean')
|
|
18
|
+
throw new Error(`field "${name}" must have type string, number or boolean`);
|
|
19
|
+
seen.add(name);
|
|
20
|
+
return { name, type, description: typeof f.description === 'string' ? f.description.slice(0, 300) : '' };
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/** One reply's records, checked against their passages. */
|
|
24
|
+
export function readRecords(value, passages, fields) {
|
|
25
|
+
const records = Array.isArray(value.records) ? value.records : [];
|
|
26
|
+
return records.flatMap((raw) => {
|
|
27
|
+
if (!raw || typeof raw !== 'object')
|
|
28
|
+
return [];
|
|
29
|
+
const record = raw;
|
|
30
|
+
const passage = typeof record.passage === 'number' ? record.passage : -1;
|
|
31
|
+
const source = passages.get(passage);
|
|
32
|
+
if (source === undefined)
|
|
33
|
+
return [];
|
|
34
|
+
const sourceSpan = typeof record.span === 'string' ? record.span : '';
|
|
35
|
+
const span = sourceSpan.slice(0, 1000);
|
|
36
|
+
const values = record.values && typeof record.values === 'object' ? record.values : {};
|
|
37
|
+
let supported = sourceSpan.length > 0 && sourceSpan.length <= 1000 && source.includes(sourceSpan);
|
|
38
|
+
const row = { passage, span };
|
|
39
|
+
for (const f of fields) {
|
|
40
|
+
const v = values[f.name];
|
|
41
|
+
const ok = v === null || v === undefined || (f.type === 'number' ? typeof v === 'number' && Number.isFinite(v) : typeof v === f.type);
|
|
42
|
+
if (!ok)
|
|
43
|
+
supported = false;
|
|
44
|
+
row[f.name] = ok && v !== undefined ? v : null;
|
|
45
|
+
}
|
|
46
|
+
row.outcome = supported ? 'supported' : 'unsupported';
|
|
47
|
+
return [row];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
const COLUMN_TYPE = { string: 'text', number: 'real', boolean: 'boolean' };
|
|
51
|
+
export const aiExtractNode = {
|
|
52
|
+
type: 'ai.extract', title: 'AI extract records', category: 'ai',
|
|
53
|
+
doc: 'Extract typed records from text passages using the host AI model. Each record quotes its source span; records whose span is not in the passage or whose values have the wrong type are marked unsupported. The run pauses for review of the records.',
|
|
54
|
+
inputs: [{ name: 'passages', type: SCALAR_LIST }],
|
|
55
|
+
outputs: [{ name: 'records', type: TABLE_ITEM }, { name: 'coverage', type: SCALAR_ITEM }],
|
|
56
|
+
params: [
|
|
57
|
+
{ name: 'fields', kind: 'json', default: [], doc: '[{ "name": "...", "type": "string|number|boolean", "description": "..." }]' },
|
|
58
|
+
{ name: 'maxPassages', kind: 'number', default: 50 },
|
|
59
|
+
{ name: 'batchSize', kind: 'number', default: 10 },
|
|
60
|
+
{ name: 'maxRecords', kind: 'number', default: 200 },
|
|
61
|
+
{ name: 'maxOutputTokens', kind: 'number', default: 2000 },
|
|
62
|
+
],
|
|
63
|
+
capabilities: [AI_CAPABILITY], requires: { backend: [AI_FEATURE], network: true }, volatile: true, review: 'required',
|
|
64
|
+
run: async (ctx, inputs, p) => {
|
|
65
|
+
const passages = inputs.passages.map((v) => (typeof v === 'string' ? v : v === null || v === undefined ? '' : String(v)));
|
|
66
|
+
const fields = fieldsOf(p.fields);
|
|
67
|
+
const maxPassages = positiveInt(p.maxPassages, 'maxPassages', 1000);
|
|
68
|
+
const batchSize = positiveInt(p.batchSize, 'batchSize', 50);
|
|
69
|
+
const maxRecords = positiveInt(p.maxRecords, 'maxRecords', 5000);
|
|
70
|
+
const maxOutputTokens = positiveInt(p.maxOutputTokens, 'maxOutputTokens', 32_000);
|
|
71
|
+
const service = aiService(ctx);
|
|
72
|
+
const task = [
|
|
73
|
+
'Extract records with these fields from the passages:',
|
|
74
|
+
...fields.map((f) => `- ${f.name} (${f.type}): ${f.description}`),
|
|
75
|
+
'Reply {"records":[{"passage":<passage number>,"span":"<exact quote from that passage>","values":{...}}]}.',
|
|
76
|
+
'Quote the span verbatim. Use null for a value the passage does not state.',
|
|
77
|
+
].join('\n');
|
|
78
|
+
const rows = [];
|
|
79
|
+
let requests = 0;
|
|
80
|
+
let sent = 0;
|
|
81
|
+
let failed = 0;
|
|
82
|
+
const sendable = Math.min(passages.length, maxPassages);
|
|
83
|
+
for (let start = 0; start < sendable && rows.length < maxRecords; start += batchSize) {
|
|
84
|
+
const batch = new Map();
|
|
85
|
+
for (let i = start; i < Math.min(start + batchSize, sendable); i++)
|
|
86
|
+
batch.set(i, passages[i].slice(0, 8000));
|
|
87
|
+
const data = [...batch].map(([i, t]) => JSON.stringify({ passage: i, text: t })).join('\n');
|
|
88
|
+
const reply = await requestJson(ctx, service, task, `<data>\n${data}\n</data>`, maxOutputTokens, extractionSchema([...batch.keys()], fields), 'flow.ai.extract.v1');
|
|
89
|
+
if (reply.kind === 'budget') {
|
|
90
|
+
ctx.log('warn', `the AI budget ran out after ${requests} request(s)`);
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
requests += 1;
|
|
94
|
+
sent += batch.size;
|
|
95
|
+
if (reply.kind === 'failed') {
|
|
96
|
+
failed += batch.size;
|
|
97
|
+
ctx.log('warn', `passages ${start + 1}-${start + batch.size}: ${reply.message}`);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (!Array.isArray(reply.value.records)) {
|
|
101
|
+
failed += batch.size;
|
|
102
|
+
ctx.log('warn', `passages ${start + 1}-${start + batch.size}: reply must contain a records array`);
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
rows.push(...readRecords(reply.value, batch, fields));
|
|
106
|
+
}
|
|
107
|
+
if (rows.length > maxRecords)
|
|
108
|
+
ctx.log('warn', `${rows.length - maxRecords} record(s) beyond maxRecords were dropped`);
|
|
109
|
+
const kept = rows.slice(0, maxRecords);
|
|
110
|
+
const records = {
|
|
111
|
+
key: 'passage',
|
|
112
|
+
columns: [
|
|
113
|
+
{ name: 'passage', type: 'integer' }, ...fields.map((f) => ({ name: f.name, type: COLUMN_TYPE[f.type] })),
|
|
114
|
+
{ name: 'span', type: 'text' }, { name: 'outcome', type: 'enum' },
|
|
115
|
+
],
|
|
116
|
+
rows: kept,
|
|
117
|
+
};
|
|
118
|
+
return {
|
|
119
|
+
records,
|
|
120
|
+
coverage: {
|
|
121
|
+
model: service.model, passages: passages.length, sent, failed, notSent: passages.length - sent, requests,
|
|
122
|
+
records: kept.length, unsupported: kept.filter((r) => r.outcome === 'unsupported').length,
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
},
|
|
126
|
+
};
|
|
127
|
+
//# sourceMappingURL=ai-extract.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-extract.js","sourceRoot":"","sources":["../src/ai-extract.ts"],"names":[],"mappings":"AAAA;;+DAE+D;AAiB/D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAElE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AACjG,OAAO,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AAK5D,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;AAEzD,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,EAAE;QAAE,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;IACxJ,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,EAAE,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC9D,MAAM,IAAI,GAAG,CAAC,EAAE,IAAI,CAAC;QACrB,IAAI,CAAC,8BAA8B,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,SAAS,CAAC,GAAG,CAAC,qEAAqE,CAAC,CAAC;QAC7L,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;YAAE,MAAM,IAAI,KAAK,CAAC,UAAU,IAAI,4CAA4C,CAAC,CAAC;QAC9I,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACf,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,WAAW,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;IAC3G,CAAC,CAAC,CAAC;AACL,CAAC;AAED,2DAA2D;AAC3D,MAAM,UAAU,WAAW,CAAC,KAA8B,EAAE,QAAqC,EAAE,MAAwB;IACzH,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC;IAClE,OAAO,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,EAAE;QAC7B,IAAI,CAAC,GAAG,IAAI,OAAO,GAAG,KAAK,QAAQ;YAAE,OAAO,EAAE,CAAC;QAC/C,MAAM,MAAM,GAAG,GAA8B,CAAC;QAC9C,MAAM,OAAO,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzE,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACrC,IAAI,MAAM,KAAK,SAAS;YAAE,OAAO,EAAE,CAAC;QACpC,MAAM,UAAU,GAAG,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC;QACtE,MAAM,IAAI,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC;QACvC,MAAM,MAAM,GAAG,MAAM,CAAC,MAAM,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAiC,CAAC,CAAC,CAAC,EAAE,CAAC;QAClH,IAAI,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,CAAC,IAAI,UAAU,CAAC,MAAM,IAAI,IAAI,IAAI,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;QAClG,MAAM,GAAG,GAAyB,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACpD,KAAK,MAAM,CAAC,IAAI,MAAM,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;YACzB,MAAM,EAAE,GAAG,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC;YACtI,IAAI,CAAC,EAAE;gBAAE,SAAS,GAAG,KAAK,CAAC;YAC3B,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAE,CAAU,CAAC,CAAC,CAAC,IAAI,CAAC;QAC3D,CAAC;QACD,GAAG,CAAC,OAAO,GAAG,SAAS,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,aAAa,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,CAAC;IACf,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,WAAW,GAAsC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC;AAE9G,MAAM,CAAC,MAAM,aAAa,GAAgB;IACxC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,oBAAoB,EAAE,QAAQ,EAAE,IAAI;IAC/D,GAAG,EAAE,sPAAsP;IAC3P,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACjD,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,WAAW,EAAE,CAAC;IACzF,MAAM,EAAE;QACN,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE,EAAE,GAAG,EAAE,4EAA4E,EAAE;QAChI,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QACpD,EAAE,IAAI,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE;QAClD,EAAE,IAAI,EAAE,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE;QACpD,EAAE,IAAI,EAAE,iBAAiB,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;KAC3D;IACD,YAAY,EAAE,CAAC,aAAa,CAAC,EAAE,QAAQ,EAAE,EAAE,OAAO,EAAE,CAAC,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU;IACrH,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,EAAE;QAC5B,MAAM,QAAQ,GAAI,MAAM,CAAC,QAAsB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACzI,MAAM,MAAM,GAAG,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAClC,MAAM,WAAW,GAAG,WAAW,CAAC,CAAC,CAAC,WAAW,EAAE,aAAa,EAAE,IAAI,CAAC,CAAC;QACpE,MAAM,SAAS,GAAG,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,WAAW,EAAE,EAAE,CAAC,CAAC;QAC5D,MAAM,UAAU,GAAG,WAAW,CAAC,CAAC,CAAC,UAAU,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;QACjE,MAAM,eAAe,GAAG,WAAW,CAAC,CAAC,CAAC,eAAe,EAAE,iBAAiB,EAAE,MAAM,CAAC,CAAC;QAClF,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;QAC/B,MAAM,IAAI,GAAG;YACX,sDAAsD;YACtD,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;YACjE,2GAA2G;YAC3G,2EAA2E;SAC5E,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,MAAM,IAAI,GAA2B,EAAE,CAAC;QACxC,IAAI,QAAQ,GAAG,CAAC,CAAC;QACjB,IAAI,IAAI,GAAG,CAAC,CAAC;QACb,IAAI,MAAM,GAAG,CAAC,CAAC;QACf,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;QACxD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU,EAAE,KAAK,IAAI,SAAS,EAAE,CAAC;YACrF,MAAM,KAAK,GAAG,IAAI,GAAG,EAAkB,CAAC;YACxC,KAAK,IAAI,CAAC,GAAG,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,SAAS,EAAE,QAAQ,CAAC,EAAE,CAAC,EAAE;gBAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;YAC7G,MAAM,IAAI,GAAG,CAAC,GAAG,KAAK,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAC5F,MAAM,KAAK,GAAG,MAAM,WAAW,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,WAAW,IAAI,WAAW,EAAE,eAAe,EAC7F,gBAAgB,CAAC,CAAC,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,EAAE,MAAM,CAAC,EAAE,oBAAoB,CAAC,CAAC;YACrE,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,+BAA+B,QAAQ,aAAa,CAAC,CAAC;gBAAC,MAAM;YAAC,CAAC;YAC9G,QAAQ,IAAI,CAAC,CAAC;YACd,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC;YACnB,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;gBAAC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC;gBAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,IAAI,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;gBAAC,SAAS;YAAC,CAAC;YAClJ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC;gBACxC,MAAM,IAAI,KAAK,CAAC,IAAI,CAAC;gBACrB,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,YAAY,KAAK,GAAG,CAAC,IAAI,KAAK,GAAG,KAAK,CAAC,IAAI,sCAAsC,CAAC,CAAC;gBACnG,SAAS;YACX,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,IAAI,CAAC,MAAM,GAAG,UAAU;YAAE,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,IAAI,CAAC,MAAM,GAAG,UAAU,2CAA2C,CAAC,CAAC;QACtH,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,UAAU,CAAC,CAAC;QACvC,MAAM,OAAO,GAAU;YACrB,GAAG,EAAE,SAAS;YACd,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;gBACzG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,EAAE,MAAM,EAAE;aAClE;YACD,IAAI,EAAE,IAAI;SACX,CAAC;QACF,OAAO;YACL,OAAO;YACP,QAAQ,EAAE;gBACR,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,QAAQ,EAAE,QAAQ,CAAC,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,MAAM,GAAG,IAAI,EAAE,QAAQ;gBACxG,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,KAAK,aAAa,CAAC,CAAC,MAAM;aAC1F;SACF,CAAC;IACJ,CAAC;CACF,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** #7070: native field constraints for portable model-change drafts. */
|
|
2
|
+
import { type ChangeScalar, type ModelChange } from '@ifc-lite/ai/artifacts';
|
|
3
|
+
export interface ProposalField {
|
|
4
|
+
readonly native: ModelChange;
|
|
5
|
+
readonly expectedColumn: string;
|
|
6
|
+
readonly allowedValues: readonly ChangeScalar[];
|
|
7
|
+
}
|
|
8
|
+
/** Native parsing checks each allowed value's IFC type, rather than copying that validator. */
|
|
9
|
+
export declare function proposalFields(raw: unknown, columns: readonly string[]): ProposalField[];
|
|
10
|
+
/** Every effect field and candidate value must be explicitly allowed by the graph. */
|
|
11
|
+
export declare function constrainedField(change: ModelChange, fields: readonly ProposalField[]): ProposalField;
|
|
12
|
+
//# sourceMappingURL=ai-propose-fields.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-propose-fields.d.ts","sourceRoot":"","sources":["../src/ai-propose-fields.ts"],"names":[],"mappings":"AAIA,wEAAwE;AACxE,OAAO,EAAoC,KAAK,YAAY,EAAE,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAE/G,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAC7B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,aAAa,EAAE,SAAS,YAAY,EAAE,CAAC;CACjD;AAKD,+FAA+F;AAC/F,wBAAgB,cAAc,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,aAAa,EAAE,CA4BxF;AAED,sFAAsF;AACtF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,SAAS,aAAa,EAAE,GAAG,aAAa,CAQrG"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
|
|
4
|
+
/** #7070: native field constraints for portable model-change drafts. */
|
|
5
|
+
import { changeKey, parseModelChangeBatch } from '@ifc-lite/ai/artifacts';
|
|
6
|
+
const sampleTarget = { globalId: '0000000000000000000000' };
|
|
7
|
+
const fieldKey = (change) => changeKey({ ...change, target: sampleTarget });
|
|
8
|
+
const record = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
|
|
9
|
+
/** Native parsing checks each allowed value's IFC type, rather than copying that validator. */
|
|
10
|
+
export function proposalFields(raw, columns) {
|
|
11
|
+
if (!Array.isArray(raw) || !raw.length || raw.length > 50)
|
|
12
|
+
throw new Error('Select 1 to 50 native fields for the proposal');
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
return raw.map((value, index) => {
|
|
15
|
+
if (!record(value))
|
|
16
|
+
throw new Error(`Field ${index + 1} must be an object`);
|
|
17
|
+
const { expectedColumn, allowedValues, ...binding } = value;
|
|
18
|
+
const keys = ['op', 'name', 'pset', 'qset', 'dataType'];
|
|
19
|
+
if (Object.keys(binding).some(key => !keys.includes(key)))
|
|
20
|
+
throw new Error(`Field ${index + 1} has an unknown constraint`);
|
|
21
|
+
if (typeof expectedColumn !== 'string' || !columns.includes(expectedColumn))
|
|
22
|
+
throw new Error(`Field ${index + 1} needs a selected expectedColumn`);
|
|
23
|
+
const deleting = binding.op === 'property.delete';
|
|
24
|
+
if (deleting ? allowedValues !== undefined : !Array.isArray(allowedValues) || !allowedValues.length || allowedValues.length > 20) {
|
|
25
|
+
throw new Error('Set fields need 1 to 20 allowedValues; delete fields must omit them');
|
|
26
|
+
}
|
|
27
|
+
const values = deleting ? ['existing value'] : allowedValues;
|
|
28
|
+
const parsed = values.map(candidate => parseModelChangeBatch(JSON.stringify({ version: 1, kind: 'model.changes', title: 'Field constraint', changes: [{
|
|
29
|
+
...binding, target: sampleTarget, expected: deleting ? 'existing value' : binding.op === 'attribute.set' ? '' : binding.op === 'quantity.set' ? 0 : null,
|
|
30
|
+
...(!deleting ? { value: candidate } : {}),
|
|
31
|
+
}] })).changes[0]);
|
|
32
|
+
const native = parsed[0];
|
|
33
|
+
// Cross-operation fields must not smuggle unused binding names into the prompt.
|
|
34
|
+
const allowedKeys = native.op === 'property.set' ? ['op', 'name', 'pset', 'dataType']
|
|
35
|
+
: native.op === 'property.delete' ? ['op', 'name', 'pset'] : native.op === 'quantity.set' ? ['op', 'name', 'qset'] : ['op', 'name'];
|
|
36
|
+
if (Object.keys(binding).some(key => !allowedKeys.includes(key)))
|
|
37
|
+
throw new Error(`Field ${index + 1} has a binding for another operation`);
|
|
38
|
+
const key = fieldKey(native);
|
|
39
|
+
if (seen.has(key))
|
|
40
|
+
throw new Error('Each native value may have only one field constraint');
|
|
41
|
+
seen.add(key);
|
|
42
|
+
return { native, expectedColumn, allowedValues: deleting ? [] : parsed.map(change => 'value' in change ? change.value : null) };
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
/** Every effect field and candidate value must be explicitly allowed by the graph. */
|
|
46
|
+
export function constrainedField(change, fields) {
|
|
47
|
+
const field = fields.find(candidate => fieldKey(candidate.native) === fieldKey(change) && candidate.native.op === change.op);
|
|
48
|
+
if (!field)
|
|
49
|
+
throw new Error('The proposed change is outside the selected native fields');
|
|
50
|
+
if (change.op === 'property.set' && field.native.op === 'property.set' && change.dataType !== field.native.dataType) {
|
|
51
|
+
throw new Error('The proposed property dataType differs from its native field constraint');
|
|
52
|
+
}
|
|
53
|
+
if ('value' in change && !field.allowedValues.some(value => Object.is(value, change.value)))
|
|
54
|
+
throw new Error('The proposed value is outside allowedValues');
|
|
55
|
+
return field;
|
|
56
|
+
}
|
|
57
|
+
//# sourceMappingURL=ai-propose-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-propose-fields.js","sourceRoot":"","sources":["../src/ai-propose-fields.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ai-propose.d.ts","sourceRoot":"","sources":["../src/ai-propose.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAK7C,eAAO,MAAM,aAAa,EAAE,WAoE3B,CAAC"}
|