@genesislcap/ai-assistant 15.34.1 → 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,52 @@
1
+ import { type TieredAIProviderSwitcherOptions } from '../provider/tiered-provider-switcher';
2
+ import type { GenesisAiConfig, GenesisAssistantRegistration, RegisterGenesisAssistantOptions } from './types';
3
+ /**
4
+ * The name of the agent {@link registerGenesisAssistant} builds.
5
+ *
6
+ * @beta
7
+ */
8
+ export declare const GENESIS_AGENT_NAME = "genesis-assistant";
9
+ /**
10
+ * The system-prompt line behind ADR P9. The tools stamp their results as untrusted app data;
11
+ * this tells the model what that means. Defence in depth — no other rule relies on it.
12
+ *
13
+ * @internal
14
+ */
15
+ export declare const UNTRUSTED_DATA_PROMPT: string;
16
+ /**
17
+ * The exported app's own AI proxy: `<PUBLIC_PATH>/gwf/ai-service`.
18
+ *
19
+ * @internal
20
+ */
21
+ export declare function defaultEndpointBase(publicPath?: string): string;
22
+ /**
23
+ * The provider options {@link registerGenesisAssistant} registers — split out so the choice of
24
+ * vendor, tier and endpoint can be checked without building providers.
25
+ *
26
+ * @remarks
27
+ * No model id appears anywhere: the vendor and tier name a row of the platform tier table, and
28
+ * that row supplies the model, the output ceiling and both timeouts. The app's proxy may clamp
29
+ * the ceiling further.
30
+ *
31
+ * @internal
32
+ */
33
+ export declare function genesisProviderOptions(config: GenesisAiConfig, options?: Pick<RegisterGenesisAssistantOptions, 'endpointBase' | 'vendors'>): Omit<TieredAIProviderSwitcherOptions, 'container'>;
34
+ /**
35
+ * Sets up the AI assistant for a Genesis app from its generated AI config: providers by tier,
36
+ * and one agent whose tools read the app's resources.
37
+ *
38
+ * @remarks
39
+ * Call it once at start-up and bind the returned `agents` to the assistant element. It is
40
+ * synchronous on purpose — the element resolves its providers as it connects, and one that
41
+ * connects before they are registered keeps the empty fallback for the whole page load.
42
+ *
43
+ * It never throws. Anything that stops the assistant working — AI switched off for the build,
44
+ * an invalid config, an extension tool reusing a generated tool's name — is logged as an error
45
+ * and returned as `blockedReason`, with no agents. The mount code applies it with the element's
46
+ * `setBlocked(true, blockedReason)`. The app keeps working: one misnamed chat tool, for instance
47
+ * after a regeneration renames a resource onto an extension's tool, must never take it down.
48
+ *
49
+ * @beta
50
+ */
51
+ export declare function registerGenesisAssistant(options: RegisterGenesisAssistantOptions): GenesisAssistantRegistration;
52
+ //# sourceMappingURL=register-genesis-assistant.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-genesis-assistant.d.ts","sourceRoot":"","sources":["../../../src/genesis/register-genesis-assistant.ts"],"names":[],"mappings":"AAIA,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,sCAAsC,CAAC;AAI9C,OAAO,KAAK,EACV,eAAe,EACf,4BAA4B,EAC5B,+BAA+B,EAChC,MAAM,SAAS,CAAC;AAEjB;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,sBAAsB,CAAC;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,QAGN,CAAC;AAE7B;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,UAAU,GAAE,MAAoB,GAAG,MAAM,CAE5E;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,IAAI,CAAC,+BAA+B,EAAE,cAAc,GAAG,SAAS,CAAM,GAC9E,IAAI,CAAC,+BAA+B,EAAE,WAAW,CAAC,CAUpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,EAAE,+BAA+B,GACvC,4BAA4B,CAuG9B"}
@@ -0,0 +1,86 @@
1
+ import type { ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
2
+ import { type Connect } from '@genesislcap/foundation-comms';
3
+ import type { GenesisAiResource } from './types';
4
+ /**
5
+ * The provenance stamped on everything a tool returns from the app (ADR P9). Rows are written by
6
+ * the app's other users, so the model must treat their text as data, never as instructions; the
7
+ * system prompt says the same thing in words.
8
+ *
9
+ * @beta
10
+ */
11
+ export declare const GENESIS_DATA_SOURCE = "app-database";
12
+ /**
13
+ * What a read tool returns to the model.
14
+ *
15
+ * @beta
16
+ */
17
+ export interface GenesisReadResult {
18
+ source: typeof GENESIS_DATA_SOURCE;
19
+ untrusted: true;
20
+ resource: string;
21
+ rows: unknown[];
22
+ /** True when more rows match than this call returned. */
23
+ truncated: boolean;
24
+ /** When `truncated`: that these rows are not the full set, and how to narrow the read. */
25
+ note?: string;
26
+ }
27
+ /**
28
+ * What any Genesis tool returns instead of throwing. A thrown tool error invites the model to
29
+ * "retry it", which for a write can mean doing it twice (ADR P7), so none of these tools throw.
30
+ *
31
+ * @beta
32
+ */
33
+ export interface GenesisToolError {
34
+ source: typeof GENESIS_DATA_SOURCE;
35
+ untrusted: true;
36
+ resource: string;
37
+ error: 'not_connected' | 'invalid_filter' | 'rejected' | 'request_failed';
38
+ message: string;
39
+ }
40
+ /**
41
+ * Options for {@link createGenesisResourceTools}.
42
+ *
43
+ * @beta
44
+ */
45
+ export interface GenesisResourceToolsOptions {
46
+ /** The connection to read through. Defaults to the app's own, resolved from DI on first use. */
47
+ connect?: Connect;
48
+ /**
49
+ * How long a metadata fetch and a read may take before they are abandoned. Both exist because
50
+ * a request Connect drops on reconnect never settles; the defaults suit a real app, and tests
51
+ * shorten them.
52
+ *
53
+ * @internal
54
+ */
55
+ timeouts?: {
56
+ metadataMs?: number;
57
+ readMs?: number;
58
+ };
59
+ }
60
+ /**
61
+ * The tools built from an app's resources, in the shape an agent config takes.
62
+ *
63
+ * @beta
64
+ */
65
+ export interface GenesisResourceTools {
66
+ /**
67
+ * Resolves the definitions lazily — schemas are fetched on the first turn, not at
68
+ * registration — and returns the same list, in name order, every time after, so the tool
69
+ * list does not re-serialise between iterations and break prompt caching.
70
+ */
71
+ toolDefinitions: () => Promise<ChatToolDefinition[]>;
72
+ toolHandlers: ChatToolHandlers;
73
+ }
74
+ /**
75
+ * Builds the assistant's tools from an app's resources.
76
+ *
77
+ * @remarks
78
+ * Each request resource becomes a read tool: filters from the resource's own schema, a row cap
79
+ * (ADR P10: 50 by default, never more than the resource's `maxRows` whatever the model asks),
80
+ * no paging (see `truncationNote`), and every result stamped as untrusted app data (P9). Event resources are not turned
81
+ * into tools yet; the write path, with its review table, comes separately.
82
+ *
83
+ * @beta
84
+ */
85
+ export declare function createGenesisResourceTools(resources: readonly GenesisAiResource[], options?: GenesisResourceToolsOptions): GenesisResourceTools;
86
+ //# sourceMappingURL=resource-tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resource-tools.d.ts","sourceRoot":"","sources":["../../../src/genesis/resource-tools.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,4BAA4B,CAAC;AACvF,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAMzE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,SAAS,CAAC;AAEjD;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAalD;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE,OAAO,mBAAmB,CAAC;IACnC,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,OAAO,EAAE,CAAC;IAChB,yDAAyD;IACzD,SAAS,EAAE,OAAO,CAAC;IACnB,0FAA0F;IAC1F,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,EAAE,OAAO,mBAAmB,CAAC;IACnC,SAAS,EAAE,IAAI,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,eAAe,GAAG,gBAAgB,GAAG,UAAU,GAAG,gBAAgB,CAAC;IAC1E,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,MAAM,WAAW,2BAA2B;IAC1C,gGAAgG;IAChG,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE;QAAE,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACrD;AAED;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC;;;;OAIG;IACH,eAAe,EAAE,MAAM,OAAO,CAAC,kBAAkB,EAAE,CAAC,CAAC;IACrD,YAAY,EAAE,gBAAgB,CAAC;CAChC;AAmID;;;;;;;;;;GAUG;AACH,wBAAgB,0BAA0B,CACxC,SAAS,EAAE,SAAS,iBAAiB,EAAE,EACvC,OAAO,GAAE,2BAAgC,GACxC,oBAAoB,CAkFtB"}
@@ -0,0 +1,115 @@
1
+ import type { AITierId, AITierVendor, ChatToolDefinition, ChatToolHandlers } from '@genesislcap/foundation-ai';
2
+ import type { Connect } from '@genesislcap/foundation-comms';
3
+ import type { Container } from '@genesislcap/web-core';
4
+ import type { AgentConfig } from '../config/config';
5
+ import type { TieredAIProviderSwitcher } from '../provider/tiered-provider-switcher';
6
+ /**
7
+ * What a Genesis resource is to the assistant: a request/reply it can read, or an event it
8
+ * can propose a write to.
9
+ *
10
+ * @beta
11
+ */
12
+ export type GenesisResourceKind = 'request' | 'event';
13
+ /**
14
+ * The wire vocabulary for an event resource's operation. Custom handlers keep their own name.
15
+ *
16
+ * @beta
17
+ */
18
+ export declare const GENESIS_EVENT_OPS: readonly ["insert", "modify", "delete", "custom"];
19
+ /**
20
+ * One of {@link GENESIS_EVENT_OPS}. Declared from the array so the type and the validator
21
+ * cannot drift apart, the way the platform's own tier ids are.
22
+ *
23
+ * @beta
24
+ */
25
+ export type GenesisEventOp = (typeof GENESIS_EVENT_OPS)[number];
26
+ /**
27
+ * One app resource exposed to the assistant — an entry of the generated `ai-config.json`.
28
+ *
29
+ * @beta
30
+ */
31
+ export interface GenesisAiResource {
32
+ /** `REQ_<NAME>` for a request, `EVENT_<NAME>` for an event. Unique within the config. */
33
+ name: string;
34
+ kind: GenesisResourceKind;
35
+ /** Events only. */
36
+ op?: GenesisEventOp;
37
+ /** What the resource is for, in plain words. Becomes the tool's description. */
38
+ context: string;
39
+ /** Requests only: the most rows one read may return (1–1000). Defaults to 50. */
40
+ maxRows?: number;
41
+ }
42
+ /**
43
+ * The AI block of a generated app's configuration (`client/src/ai/generated/ai-config.json`).
44
+ *
45
+ * @remarks
46
+ * Carries no model id, endpoint, key or budget: the model comes from the platform tier table,
47
+ * the endpoint from the app's own proxy, and the key never leaves the server.
48
+ *
49
+ * @beta
50
+ */
51
+ export interface GenesisAiConfig {
52
+ enabled: boolean;
53
+ /** Defaults to `'gemini'`. */
54
+ vendor?: AITierVendor;
55
+ /** Defaults to `'high'`. */
56
+ tier?: AITierId;
57
+ systemPrompt?: string;
58
+ resources?: GenesisAiResource[];
59
+ }
60
+ /**
61
+ * An app's own additions to the assistant — the hand-written tools under
62
+ * `client/src/ai/extensions/`.
63
+ *
64
+ * @beta
65
+ */
66
+ export interface GenesisAssistantExtensions {
67
+ toolDefinitions?: ChatToolDefinition[];
68
+ toolHandlers?: ChatToolHandlers;
69
+ /** Appended to the configured system prompt. */
70
+ systemPrompt?: string;
71
+ }
72
+ /**
73
+ * Options for {@link registerGenesisAssistant}.
74
+ *
75
+ * @beta
76
+ */
77
+ export interface RegisterGenesisAssistantOptions {
78
+ config: GenesisAiConfig;
79
+ /** DI container the providers are registered on. Defaults to the DOM container. */
80
+ container?: Container;
81
+ /**
82
+ * Base of the app's AI proxy routes; `/<vendor>/chat` is appended. Defaults to the exported
83
+ * app's own proxy, `<PUBLIC_PATH>/gwf/ai-service`. A preview build passes its own.
84
+ */
85
+ endpointBase?: string;
86
+ /**
87
+ * The Genesis connection the tools read through. Defaults to the app's own, resolved from DI
88
+ * when a tool first runs rather than at registration, so registering stays synchronous.
89
+ */
90
+ connect?: Connect;
91
+ extensions?: GenesisAssistantExtensions;
92
+ /**
93
+ * Vendors the user may switch between. Defaults to the configured vendor alone: an app whose
94
+ * proxy holds one key would otherwise offer a switch to a vendor that cannot answer.
95
+ */
96
+ vendors?: readonly AITierVendor[];
97
+ }
98
+ /**
99
+ * What {@link registerGenesisAssistant} hands back to the app's mount code.
100
+ *
101
+ * @beta
102
+ */
103
+ export interface GenesisAssistantRegistration {
104
+ /** Bind to the assistant element's `agents` property. Empty when blocked. */
105
+ agents: AgentConfig[];
106
+ /** The vendor switcher, when providers were registered. */
107
+ switcher?: TieredAIProviderSwitcher;
108
+ /**
109
+ * Set when the assistant cannot run — AI switched off, an invalid config, a tool-name clash.
110
+ * Apply it with the element's `setBlocked(true, blockedReason)`. The app itself keeps working:
111
+ * a problem with one chat tool must never take the whole app down at start-up.
112
+ */
113
+ blockedReason?: string;
114
+ }
115
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/genesis/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,QAAQ,EACR,YAAY,EACZ,kBAAkB,EAClB,gBAAgB,EACjB,MAAM,4BAA4B,CAAC;AACpC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,+BAA+B,CAAC;AAC7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAC;AAErF;;;;;GAKG;AACH,MAAM,MAAM,mBAAmB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEtD;;;;GAIG;AACH,eAAO,MAAM,iBAAiB,mDAAoD,CAAC;AAEnF;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,OAAO,iBAAiB,CAAC,CAAC,MAAM,CAAC,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,yFAAyF;IACzF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,mBAAmB,CAAC;IAC1B,mBAAmB;IACnB,EAAE,CAAC,EAAE,cAAc,CAAC;IACpB,gFAAgF;IAChF,OAAO,EAAE,MAAM,CAAC;IAChB,iFAAiF;IACjF,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;GAQG;AACH,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,8BAA8B;IAC9B,MAAM,CAAC,EAAE,YAAY,CAAC;IACtB,4BAA4B;IAC5B,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,iBAAiB,EAAE,CAAC;CACjC;AAED;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IACzC,eAAe,CAAC,EAAE,kBAAkB,EAAE,CAAC;IACvC,YAAY,CAAC,EAAE,gBAAgB,CAAC;IAChC,gDAAgD;IAChD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,MAAM,WAAW,+BAA+B;IAC9C,MAAM,EAAE,eAAe,CAAC;IACxB,mFAAmF;IACnF,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;OAGG;IACH,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACxC;;;OAGG;IACH,OAAO,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CACnC;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B;IAC3C,6EAA6E;IAC7E,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,wBAAwB,CAAC;IACpC;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB"}
@@ -0,0 +1,86 @@
1
+ import { AI_TIER_IDS, AI_TIER_VENDORS } from '@genesislcap/foundation-ai';
2
+ import { GENESIS_EVENT_OPS } from './types';
3
+ /** The default read cap, and the ceiling the config may raise it to. */
4
+ export const DEFAULT_MAX_ROWS = 50;
5
+ export const MAX_ROWS_CEILING = 1000;
6
+ /** Tool names are the lowercased resource name; vendors reject names over 64 characters. */
7
+ const MAX_TOOL_NAME_LENGTH = 64;
8
+ const RESOURCE_NAME = /^(REQ|EVENT)_[A-Z0-9_]+$/;
9
+ /**
10
+ * The model-facing tool name for a resource. Deterministic and collision-free (resource names
11
+ * are unique), and trivially mapped back to the resource in a debug log. What the tool is FOR
12
+ * reaches the model through its description, not its name.
13
+ */
14
+ export const toolNameOf = (resourceName) => resourceName.toLowerCase();
15
+ /**
16
+ * Checks a generated AI config before anything is registered, returning every problem found.
17
+ *
18
+ * @remarks
19
+ * The config is machine-generated and schema-checked upstream, so a violation here is a bug in
20
+ * that pipeline, not something to work around. The caller turns problems into a blocked
21
+ * assistant with the reason on screen — scoped to the assistant, never an exception that could
22
+ * take the app down at start-up — rather than running with part of the config quietly missing.
23
+ *
24
+ * @internal
25
+ */
26
+ export function validateGenesisAiConfig(config) {
27
+ if (!config || typeof config !== 'object')
28
+ return ['the AI config is missing or not an object'];
29
+ const c = config;
30
+ const problems = [];
31
+ if (c.enabled !== true)
32
+ problems.push(`enabled must be true (got ${String(c.enabled)})`);
33
+ if (c.vendor !== undefined && !AI_TIER_VENDORS.includes(c.vendor)) {
34
+ problems.push(`unknown vendor '${String(c.vendor)}' (expected ${AI_TIER_VENDORS.join(' or ')})`);
35
+ }
36
+ if (c.tier !== undefined && !AI_TIER_IDS.includes(c.tier)) {
37
+ problems.push(`unknown tier '${String(c.tier)}' (expected ${AI_TIER_IDS.join(', ')})`);
38
+ }
39
+ if (c.systemPrompt !== undefined && typeof c.systemPrompt !== 'string') {
40
+ problems.push('systemPrompt must be a string');
41
+ }
42
+ if (c.resources === undefined)
43
+ return problems;
44
+ if (!Array.isArray(c.resources))
45
+ return [...problems, 'resources must be an array'];
46
+ const seen = new Set();
47
+ c.resources.forEach((resource, i) => {
48
+ const at = `resources[${i}]`;
49
+ const name = resource === null || resource === void 0 ? void 0 : resource.name;
50
+ if (typeof name !== 'string' || !RESOURCE_NAME.test(name)) {
51
+ problems.push(`${at}: name must look like REQ_<NAME> or EVENT_<NAME> (got ${String(name)})`);
52
+ return;
53
+ }
54
+ if (toolNameOf(name).length > MAX_TOOL_NAME_LENGTH) {
55
+ problems.push(`${name}: longer than the ${MAX_TOOL_NAME_LENGTH} characters a tool name allows`);
56
+ }
57
+ // A duplicate is not harmless: two tools with one name are rejected by the vendor, and the
58
+ // same list feeds the generated MCP script, which refuses a repeated name at load.
59
+ if (seen.has(name))
60
+ problems.push(`${name}: appears more than once`);
61
+ seen.add(name);
62
+ const expectedKind = name.startsWith('REQ_') ? 'request' : 'event';
63
+ if (resource.kind !== expectedKind) {
64
+ problems.push(`${name}: kind must be '${expectedKind}' (got ${String(resource.kind)})`);
65
+ }
66
+ if (typeof resource.context !== 'string')
67
+ problems.push(`${name}: context must be a string`);
68
+ if (expectedKind === 'event') {
69
+ if (!GENESIS_EVENT_OPS.includes(resource.op)) {
70
+ problems.push(`${name}: op must be one of ${GENESIS_EVENT_OPS.join(', ')}`);
71
+ }
72
+ if (resource.maxRows !== undefined)
73
+ problems.push(`${name}: maxRows applies to requests only`);
74
+ }
75
+ else {
76
+ if (resource.op !== undefined)
77
+ problems.push(`${name}: op applies to events only`);
78
+ const { maxRows } = resource;
79
+ if (maxRows !== undefined &&
80
+ !(Number.isInteger(maxRows) && maxRows >= 1 && maxRows <= MAX_ROWS_CEILING)) {
81
+ problems.push(`${name}: maxRows must be a whole number from 1 to ${MAX_ROWS_CEILING}`);
82
+ }
83
+ }
84
+ });
85
+ return problems;
86
+ }