@genesislcap/ai-assistant 15.34.0 → 15.35.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.
@@ -0,0 +1,180 @@
1
+ # Genesis Bridge
2
+
3
+ ## Overview
4
+
5
+ `@genesislcap/ai-assistant/genesis` connects the assistant to a Genesis app: it registers the
6
+ model providers from the platform tier table, turns the app's resources into chat tools, and
7
+ hands back one agent to bind to `<foundation-ai-assistant>`. It is what a generated app's mount
8
+ code calls; the app supplies configuration, never a model id, endpoint or key.
9
+
10
+ The API is `@beta`. This release covers **reads** — each `REQ_` resource becomes a read tool.
11
+ `EVENT_` resources are validated and their tool names reserved, but they are not exposed as tools
12
+ yet: writes need a review step in front of them and land separately.
13
+
14
+ ---
15
+
16
+ ## Mounting
17
+
18
+ ```ts
19
+ import { registerGenesisAssistant } from '@genesislcap/ai-assistant/genesis';
20
+ import config from './generated/ai-config.json';
21
+ import * as extensions from './extensions';
22
+
23
+ const { agents, blockedReason } = registerGenesisAssistant({ config, extensions });
24
+
25
+ const assistant = document.querySelector('foundation-ai-assistant');
26
+ assistant.agents = agents;
27
+ if (blockedReason) assistant.setBlocked(true, blockedReason);
28
+ ```
29
+
30
+ `registerGenesisAssistant` is synchronous and **never throws**. Anything that stops the assistant
31
+ from running comes back as `blockedReason` (and is logged as an error) with an empty `agents`
32
+ list, so a bad config or a tool-name clash costs the app its assistant, never its start-up:
33
+
34
+ - AI is switched off for the build (`feature.ai` flag) or in the config (`enabled: false`);
35
+ - the config is invalid (see below);
36
+ - an extension tool reuses a name derived from a resource — reads and writes alike, so a later
37
+ release that exposes writes cannot collide with a tool the app already ships;
38
+ - an extension tool is declared twice or has no handler;
39
+ - the providers cannot be registered (for example `vendors` leaves out the configured vendor).
40
+
41
+ A blocked registration registers no providers.
42
+
43
+ ### Options
44
+
45
+ | Option | Default | Notes |
46
+ | -------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
47
+ | `config` | — | The app's `GenesisAiConfig`. |
48
+ | `container` | the DOM DI container | Where the tier providers are registered. |
49
+ | `endpointBase` | `<PUBLIC_PATH>/gwf/ai-service` | The app's own AI proxy; `/<vendor>/chat` is appended. A preview build passes its own. |
50
+ | `vendors` | `[config.vendor]` | Vendors the user may switch between. One by default: a proxy holding one key cannot answer for another vendor. |
51
+ | `connect` | the app's `Connect`, from DI | Resolved when a tool first runs, not at registration. |
52
+ | `extensions` | none | `{ toolDefinitions, toolHandlers, systemPrompt }` — the app's hand-written tools, added after the generated ones. |
53
+
54
+ The agent runs on the registry's default provider, which is the configured tier. Its system prompt
55
+ is the configured prompt, then a fixed rule that tool results are app data and never
56
+ instructions, then the extensions' prompt.
57
+
58
+ ---
59
+
60
+ ## Configuration
61
+
62
+ ```json
63
+ {
64
+ "enabled": true,
65
+ "vendor": "gemini",
66
+ "tier": "high",
67
+ "systemPrompt": "You help traders find and understand their trades.",
68
+ "resources": [
69
+ { "name": "REQ_TRADE", "kind": "request", "context": "Trades booked in the app.", "maxRows": 200 },
70
+ { "name": "EVENT_TRADE_INSERT", "kind": "event", "op": "insert", "context": "Book a trade." }
71
+ ]
72
+ }
73
+ ```
74
+
75
+ | Field | Rule |
76
+ | -------------- | ------------------------------------------------------------------------------------------- |
77
+ | `vendor` | A tier-table vendor. Defaults to `gemini`. |
78
+ | `tier` | `low`, `high` or `reasoning`. Defaults to `high`. |
79
+ | `name` | `REQ_<NAME>` or `EVENT_<NAME>`, unique; the tool name is the lowercased name (≤ 64 chars). |
80
+ | `kind` | Must match the prefix: `request` for `REQ_`, `event` for `EVENT_`. |
81
+ | `context` | Required. Becomes the tool description, so write it for the model. |
82
+ | `op` | Events only: `insert`, `modify`, `delete` or `custom`. |
83
+ | `maxRows` | Requests only: a whole number from 1 to 1000. Defaults to 50. |
84
+
85
+ ---
86
+
87
+ ## Read tools
88
+
89
+ A `REQ_TRADE` resource becomes the tool `req_trade` with two optional parameters:
90
+
91
+ - `filters` — a list of `{field, op, value}`, all of which must match.
92
+ - `max_rows` — clamped to the resource's `maxRows`; the resource default when omitted.
93
+
94
+ ### Filtering is criteria, and the model never writes one
95
+
96
+ Filters become a single `CRITERIA_MATCH` expression, composed by the bridge. The model supplies
97
+ structure only, because criteria are parsed as Groovy on the server and the platform concatenates
98
+ an app's own row-level restriction into the same string (`"($userCriteria) && ($criteriaTemplate)"`).
99
+ A criteria string built from model output could `||` its way past that restriction. **Do not add a
100
+ free-text criteria parameter.**
101
+
102
+ - The fields a resource can be filtered on come from the platform's metadata reply, which is
103
+ fetched under the resource's **unprefixed** name (`REQ_TRADE` is read as `REQ_TRADE`, but its
104
+ metadata is published as `TRADE`). `CRITERIA_FIELDS` is used when the resource publishes one;
105
+ a request server usually does not, and its criteria are evaluated against the row, so its
106
+ `REPLY_FIELD`s are what can be filtered on. Types and any `VALID_VALUES` come from the same
107
+ reply. A resource that describes no fields gets a tool with no `filters` parameter.
108
+ - `op` is one of `equals`, `not_equals`, `contains`, `greater_than`, `greater_or_equal`,
109
+ `less_than`, `less_or_equal`, `is_blank`, `is_not_blank`. Which ones apply depends on the field's
110
+ type; the tool tells the model what a field holds, including an enum's values.
111
+ - Values are escaped and written per type — strings quoted, numbers and booleans as literals,
112
+ dates as `Expr.dateIs*` calls on a `yyyyMMdd` bound. The grammar and escaping match grid-pro's
113
+ `filter.utils.ts`, the platform's other structured-filter caller.
114
+ - A filter the bridge cannot build **refuses the whole read** (`error: 'invalid_filter'`) and says
115
+ what the resource does accept. It is never dropped silently and never sent in part: rows read
116
+ without a filter would answer a different question from the one the model asked.
117
+
118
+ `REQUEST` is always sent empty, so a read works the same on a `criteriaOnlyRequest` request server
119
+ and on one that declares request fields.
120
+
121
+ There is no paging. A request resource honours `OFFSET` only when it is declared with
122
+ `criteriaOnlyRequest`, which the bridge cannot see; on any other resource every "next page" is the
123
+ first page again, and the model would count the same rows twice. A read is one capped page, and
124
+ the way to the rest is a narrower read.
125
+
126
+ A read never throws. Every result carries `source: 'app-database'` and `untrusted: true`:
127
+
128
+ ```ts
129
+ // success
130
+ { source, untrusted, resource, rows, truncated, note? }
131
+ // failure
132
+ { source, untrusted, resource,
133
+ error: 'not_connected' | 'invalid_filter' | 'rejected' | 'request_failed', message }
134
+ ```
135
+
136
+ `truncated` is the server's `MORE_ROWS` when it sends one; otherwise the rule the platform's own
137
+ request datasource uses (an advancing `NEXT_OFFSET` or a full page means there may be more). A
138
+ truncated result carries a `note` telling the model these rows are not the full set, and what it
139
+ can do: narrow with filters when the tool has any, raise `max_rows` when it is below the cap.
140
+
141
+ `createGenesisResourceTools(resources, { connect })` builds the same tools without registering
142
+ anything, for an app that assembles its own agent.
143
+
144
+ ---
145
+
146
+ ## Why a separate entry point
147
+
148
+ The bridge imports `@genesislcap/foundation-comms`. This package declares no `sideEffects`, so
149
+ everything reachable from its root ends up in every consumer's bundle — including hosts that are
150
+ not Genesis apps. Keeping the bridge on its own entry point keeps it out of theirs; a test walks
151
+ the root's import graph to hold that line.
152
+
153
+ ### Why `foundation-comms` is an optional peer at `workspace:^`
154
+
155
+ `foundation-comms` is a **peer dependency** so the bridge uses the app's own `Connect`. It is
156
+ installed either way (`foundation-ui` depends on it); the peer is about which copy.
157
+
158
+ **What a second copy costs is silence.** A DI token is identity-based, so a second copy of comms
159
+ carries a different one: the bridge would resolve a `Connect` that is not the app's, the tools
160
+ would talk to a connection nobody logged into, and nothing anywhere would say so. There is no
161
+ error to catch and no symptom to grep for — only tools that answer as though the app had no data.
162
+
163
+ **Declaring beats deduping.** A regular dependency would usually be hoisted to one copy, and
164
+ usually is not a guarantee: it rests on the consumer's resolver flattening the tree, which a
165
+ version skew, a nested install or a different package manager can undo without anyone touching
166
+ this package. A peer states the requirement instead of hoping it is met, and the install fails
167
+ loudly when it is not.
168
+
169
+ It is **optional** because only `./genesis` uses it: a host that never imports the bridge should
170
+ not be asked to install comms.
171
+
172
+ The range is a **caret, not `workspace:*`**. `workspace:*` publishes as an exact version, and an
173
+ exact peer means any patch-level difference between the app's copy and this package's is an
174
+ ERESOLVE at install time — a hard failure over a difference that cannot matter. `workspace:^`
175
+ publishes as `^<version>`, which is what the requirement actually is.
176
+
177
+ Two things are new here and were chosen rather than defaulted into: this is the first
178
+ `workspace:^` in the monorepo, and the first internal package declared as a peer. Whatever lands
179
+ here is what the next package copies, which is the reason the argument is written down rather
180
+ than left in the `package.json` line.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "15.34.0",
4
+ "version": "15.35.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -32,6 +32,10 @@
32
32
  "import": "./dist/chat-driver.mjs",
33
33
  "require": "./dist/chat-driver.cjs"
34
34
  },
35
+ "./genesis": {
36
+ "types": "./dist/dts/genesis/index.d.ts",
37
+ "default": "./dist/esm/genesis/index.js"
38
+ },
35
39
  "./package.json": "./package.json",
36
40
  "./react": {
37
41
  "types": "./dist/dts/react.d.ts",
@@ -46,6 +50,9 @@
46
50
  ],
47
51
  "chat-driver": [
48
52
  "./dist/dts/chat-driver-node.d.ts"
53
+ ],
54
+ "genesis": [
55
+ "./dist/dts/genesis/index.d.ts"
49
56
  ]
50
57
  }
51
58
  },
@@ -75,29 +82,38 @@
75
82
  }
76
83
  },
77
84
  "devDependencies": {
78
- "@genesislcap/foundation-testing": "15.34.0",
79
- "@genesislcap/genx": "15.34.0",
80
- "@genesislcap/rollup-builder": "15.34.0",
81
- "@genesislcap/ts-builder": "15.34.0",
82
- "@genesislcap/uvu-playwright-builder": "15.34.0",
83
- "@genesislcap/vite-builder": "15.34.0",
84
- "@genesislcap/webpack-builder": "15.34.0",
85
+ "@genesislcap/foundation-comms": "15.35.0",
86
+ "@genesislcap/foundation-testing": "15.35.0",
87
+ "@genesislcap/genx": "15.35.0",
88
+ "@genesislcap/rollup-builder": "15.35.0",
89
+ "@genesislcap/ts-builder": "15.35.0",
90
+ "@genesislcap/uvu-playwright-builder": "15.35.0",
91
+ "@genesislcap/vite-builder": "15.35.0",
92
+ "@genesislcap/webpack-builder": "15.35.0",
85
93
  "@types/dompurify": "^3.0.5",
86
94
  "@types/marked": "^5.0.2",
87
95
  "esbuild": "0.25.12"
88
96
  },
89
97
  "dependencies": {
90
- "@genesislcap/foundation-ai": "15.34.0",
91
- "@genesislcap/foundation-logger": "15.34.0",
92
- "@genesislcap/foundation-notifications": "15.34.0",
93
- "@genesislcap/foundation-redux": "15.34.0",
94
- "@genesislcap/foundation-ui": "15.34.0",
95
- "@genesislcap/foundation-utils": "15.34.0",
96
- "@genesislcap/rapid-design-system": "15.34.0",
97
- "@genesislcap/web-core": "15.34.0",
98
+ "@genesislcap/foundation-ai": "15.35.0",
99
+ "@genesislcap/foundation-logger": "15.35.0",
100
+ "@genesislcap/foundation-notifications": "15.35.0",
101
+ "@genesislcap/foundation-redux": "15.35.0",
102
+ "@genesislcap/foundation-ui": "15.35.0",
103
+ "@genesislcap/foundation-utils": "15.35.0",
104
+ "@genesislcap/rapid-design-system": "15.35.0",
105
+ "@genesislcap/web-core": "15.35.0",
98
106
  "dompurify": "^3.3.1",
99
107
  "marked": "^17.0.3"
100
108
  },
109
+ "peerDependencies": {
110
+ "@genesislcap/foundation-comms": "^15.35.0"
111
+ },
112
+ "peerDependenciesMeta": {
113
+ "@genesislcap/foundation-comms": {
114
+ "optional": true
115
+ }
116
+ },
101
117
  "repository": {
102
118
  "type": "git",
103
119
  "url": "git+https://github.com/genesislcap/foundation-ui.git",