@bpmsoftwaresolutions/ai-engine-client 0.1.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.
Files changed (3) hide show
  1. package/README.md +69 -0
  2. package/package.json +28 -0
  3. package/src/index.js +162 -0
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @bpmsoftwaresolutions/ai-engine-client
2
+
3
+ Thin npm client for AI Engine's supported operator and retrieval APIs.
4
+
5
+ ## Why this package exists
6
+
7
+ This package is the no-clone distribution shape for coworkers and other consumers.
8
+
9
+ It does not embed the full AI Engine runtime. It talks to a running AI Engine service over its stable HTTP surfaces.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ npm install @bpmsoftwaresolutions/ai-engine-client
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```js
20
+ import { AIEngineClient } from '@bpmsoftwaresolutions/ai-engine-client';
21
+
22
+ const client = new AIEngineClient({
23
+ baseUrl: 'https://resume-generator-api.politehill-3458aba1.eastus.azurecontainerapps.io'
24
+ });
25
+
26
+ const health = await client.ping();
27
+ const workflowStatus = await client.currentWorkflowStatus();
28
+ const procedure = await client.resolveOperatingProcedure({ intentText: 'startup' });
29
+ ```
30
+
31
+ The Azure-hosted base URL above is the current working no-clone path.
32
+
33
+ ## Supported methods
34
+
35
+ - `ping()`
36
+ - `currentWorkflowStatus()`
37
+ - `currentArchitectureIntegrityStatus()`
38
+ - `currentSecurityGovernanceStatus()`
39
+ - `getCommandCard()`
40
+ - `resolveOperatingProcedure()`
41
+ - `getSymbolDefinition()`
42
+ - `getRelatedCode()`
43
+
44
+ ## Runtime contract
45
+
46
+ The package expects a running AI Engine web service. The preferred production-style path is the Azure-hosted deployment at:
47
+
48
+ ```text
49
+ https://resume-generator-api.politehill-3458aba1.eastus.azurecontainerapps.io
50
+ ```
51
+
52
+ You can also point it at the Flask app from this repo or another deployment exposing the same routes.
53
+
54
+ Current required HTTP surfaces:
55
+
56
+ - `/healthz`
57
+ - `/api/operator/current-workflow-status`
58
+ - `/api/operator/current-architecture-integrity-status`
59
+ - `/api/operator/current-security-governance-status`
60
+ - `/api/operator/retrieval/wrapper/command-card`
61
+ - `/api/operator/retrieval/wrapper/operating-procedure`
62
+ - `/api/operator/retrieval/wrapper/symbol-definition`
63
+ - `/api/operator/retrieval/wrapper/related-code`
64
+
65
+ ## Distribution note
66
+
67
+ This is the preferred distribution shape when the consumer should not clone the full repo. The full repo is still required only when the consumer is running or developing the backend itself.
68
+
69
+ Known limitation: `getRelatedCode()` is available on the hosted surface, but it currently returns `blocked_insufficient_data` until the relationship index is populated.
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@bpmsoftwaresolutions/ai-engine-client",
3
+ "version": "0.1.0",
4
+ "description": "Thin npm client for the AI Engine operator and retrieval APIs",
5
+ "type": "module",
6
+ "main": "./src/index.js",
7
+ "exports": {
8
+ ".": "./src/index.js"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18"
16
+ },
17
+ "keywords": [
18
+ "ai-engine",
19
+ "sdk",
20
+ "client",
21
+ "retrieval",
22
+ "workflow"
23
+ ],
24
+ "license": "UNLICENSED",
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }
package/src/index.js ADDED
@@ -0,0 +1,162 @@
1
+ const DEFAULT_TIMEOUT_MS = 30000;
2
+
3
+ function trimTrailingSlash(value) {
4
+ return String(value || '').replace(/\/+$/, '');
5
+ }
6
+
7
+ function appendQuery(url, query) {
8
+ const target = new URL(url);
9
+ for (const [key, value] of Object.entries(query || {})) {
10
+ if (value === undefined || value === null || value === '') {
11
+ continue;
12
+ }
13
+ target.searchParams.set(key, String(value));
14
+ }
15
+ return target;
16
+ }
17
+
18
+ async function readJson(response) {
19
+ const contentType = response.headers.get('content-type') || '';
20
+ if (contentType.includes('application/json')) {
21
+ return response.json();
22
+ }
23
+ const text = await response.text();
24
+ return { message: text };
25
+ }
26
+
27
+ export class AIEngineClient {
28
+ constructor({ baseUrl, apiKey, actorId, fetchImpl, timeoutMs } = {}) {
29
+ if (!baseUrl) {
30
+ throw new Error('baseUrl is required.');
31
+ }
32
+ this.baseUrl = trimTrailingSlash(baseUrl);
33
+ this.apiKey = apiKey || null;
34
+ this.actorId = actorId || 'sdk:npm-ai-engine-client';
35
+ this.fetchImpl = fetchImpl || globalThis.fetch;
36
+ this.timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS;
37
+ if (typeof this.fetchImpl !== 'function') {
38
+ throw new Error('A fetch implementation is required. Use Node 18+ or supply fetchImpl.');
39
+ }
40
+ }
41
+
42
+ static fromEnv(options = {}) {
43
+ return new AIEngineClient({
44
+ baseUrl: options.baseUrl || process.env.AI_ENGINE_BASE_URL || process.env.AI_ENGINE_API_BASE_URL,
45
+ apiKey: options.apiKey || process.env.AI_ENGINE_API_KEY || null,
46
+ actorId: options.actorId || process.env.AI_ENGINE_ACTOR_ID || 'sdk:npm-ai-engine-client'
47
+ });
48
+ }
49
+
50
+ async ping() {
51
+ const [health, workflow] = await Promise.all([
52
+ this._request('/healthz'),
53
+ this.currentWorkflowStatus()
54
+ ]);
55
+ return {
56
+ status: health.status || 'ok',
57
+ workflow_name: workflow?.summary?.workflow_name || null,
58
+ run_status: workflow?.summary?.run_status || null,
59
+ base_url: this.baseUrl
60
+ };
61
+ }
62
+
63
+ async currentWorkflowStatus() {
64
+ return this._request('/api/operator/current-workflow-status');
65
+ }
66
+
67
+ async currentArchitectureIntegrityStatus() {
68
+ return this._request('/api/operator/current-architecture-integrity-status');
69
+ }
70
+
71
+ async currentSecurityGovernanceStatus({ environment, topN } = {}) {
72
+ return this._request('/api/operator/current-security-governance-status', {
73
+ query: {
74
+ environment,
75
+ top_n: topN
76
+ }
77
+ });
78
+ }
79
+
80
+ async getCommandCard({ commandKey, alias, intentText, requestedBy } = {}) {
81
+ return this._request('/api/operator/retrieval/wrapper/command-card', {
82
+ query: {
83
+ command_key: commandKey,
84
+ alias,
85
+ intent_text: intentText,
86
+ requested_by: requestedBy
87
+ }
88
+ });
89
+ }
90
+
91
+ async resolveOperatingProcedure({ procedureKey, intentText, requestedBy } = {}) {
92
+ return this._request('/api/operator/retrieval/wrapper/operating-procedure', {
93
+ query: {
94
+ procedure_key: procedureKey,
95
+ intent_text: intentText,
96
+ requested_by: requestedBy
97
+ }
98
+ });
99
+ }
100
+
101
+ async getSymbolDefinition({ symbolKey, qualifiedName, symbolName, projectScope, includeCode = true, maxLines = 120, requestedBy } = {}) {
102
+ return this._request('/api/operator/retrieval/wrapper/symbol-definition', {
103
+ query: {
104
+ symbol_key: symbolKey,
105
+ qualified_name: qualifiedName,
106
+ symbol_name: symbolName,
107
+ project_scope: projectScope,
108
+ include_code: includeCode,
109
+ max_lines: maxLines,
110
+ requested_by: requestedBy
111
+ }
112
+ });
113
+ }
114
+
115
+ async getRelatedCode({ symbolKey, qualifiedName, relationshipType, depth = 1, includeCode = false, maxLines = 80, requestedBy } = {}) {
116
+ return this._request('/api/operator/retrieval/wrapper/related-code', {
117
+ query: {
118
+ symbol_key: symbolKey,
119
+ qualified_name: qualifiedName,
120
+ relationship_type: relationshipType,
121
+ depth,
122
+ include_code: includeCode,
123
+ max_lines: maxLines,
124
+ requested_by: requestedBy
125
+ }
126
+ });
127
+ }
128
+
129
+ async _request(path, { method = 'GET', query, headers, body } = {}) {
130
+ const url = appendQuery(`${this.baseUrl}${path}`, query);
131
+ const controller = new AbortController();
132
+ const timeoutHandle = setTimeout(() => controller.abort(), this.timeoutMs);
133
+ try {
134
+ const response = await this.fetchImpl(url, {
135
+ method,
136
+ headers: {
137
+ accept: 'application/json',
138
+ 'content-type': body ? 'application/json' : undefined,
139
+ 'x-actor-id': this.actorId,
140
+ authorization: this.apiKey ? `Bearer ${this.apiKey}` : undefined,
141
+ ...headers
142
+ },
143
+ body: body ? JSON.stringify(body) : undefined,
144
+ signal: controller.signal
145
+ });
146
+ const payload = await readJson(response);
147
+ if (!response.ok) {
148
+ const error = new Error(payload?.message || payload?.error || `Request failed with status ${response.status}.`);
149
+ error.status = response.status;
150
+ error.payload = payload;
151
+ throw error;
152
+ }
153
+ return payload;
154
+ } finally {
155
+ clearTimeout(timeoutHandle);
156
+ }
157
+ }
158
+ }
159
+
160
+ export function createAIEngineClient(options) {
161
+ return new AIEngineClient(options);
162
+ }