@bpmsoftwaresolutions/ai-engine-client 1.0.0-beta.8 → 1.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.
- package/README.md +320 -5
- package/examples/implementation-plan-packet.minimal.json +76 -0
- package/package.json +7 -3
- package/src/index.js +378 -18
package/README.md
CHANGED
|
@@ -10,13 +10,25 @@ No repo clone required. Install the package, point it at the Azure service, and
|
|
|
10
10
|
npm install @bpmsoftwaresolutions/ai-engine-client
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
## Publishing
|
|
14
|
+
|
|
15
|
+
This package is published automatically from GitHub Actions by `.github/workflows/publish-ai-engine-client.yml`.
|
|
16
|
+
|
|
17
|
+
The workflow runs on pushes to `main` when files under `packages/ai-engine-client/**` change, and it can also be started manually with `workflow_dispatch`.
|
|
18
|
+
|
|
19
|
+
Authentication supports either:
|
|
20
|
+
|
|
21
|
+
- npm trusted publishing
|
|
22
|
+
- a repository secret named `NPM_TOKEN`, `NPM_PUBLISH_TOKEN`, or `NODE_AUTH_TOKEN`
|
|
23
|
+
|
|
13
24
|
## Quick Start
|
|
14
25
|
|
|
15
26
|
```js
|
|
16
27
|
import { AIEngineClient } from '@bpmsoftwaresolutions/ai-engine-client';
|
|
17
28
|
|
|
18
29
|
const client = new AIEngineClient({
|
|
19
|
-
baseUrl: 'https://
|
|
30
|
+
baseUrl: 'https://55e8b585-6076-4992-a164-a5398899efdb-00-c56xlmp9cuq9.worf.replit.dev',
|
|
31
|
+
// pass the key via a custom header or interceptor
|
|
20
32
|
});
|
|
21
33
|
|
|
22
34
|
// Health
|
|
@@ -38,13 +50,123 @@ const charter = await client.createProjectCharter({
|
|
|
38
50
|
|
|
39
51
|
// Read the resulting project roadmap
|
|
40
52
|
const roadmap = await client.getProjectRoadmap(charter.project_id);
|
|
53
|
+
|
|
54
|
+
const startup = await client.startSessionGovernance({
|
|
55
|
+
objective: 'Persist governed assistant turns for the charter project.',
|
|
56
|
+
allowed_mutation_surfaces: ['src/', 'scripts/'],
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const persistedTurn = await client.persistAssistantTurn({
|
|
60
|
+
project_id: charter.project_id,
|
|
61
|
+
session_key: startup.session_key,
|
|
62
|
+
user_request: 'Proceed with the next roadmap slice.',
|
|
63
|
+
assistant_summary: 'Completed the implementation step and persisted the governed turn.',
|
|
64
|
+
changed_files: ['src/web/app.py'],
|
|
65
|
+
});
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Integration Notes
|
|
69
|
+
|
|
70
|
+
### Auth Modes
|
|
71
|
+
|
|
72
|
+
The package supports two authentication patterns:
|
|
73
|
+
|
|
74
|
+
1. Bearer-token auth for operator and governed routes.
|
|
75
|
+
2. Compatibility API-key auth for migration-oriented external routes and deployments that still allow shared-key access.
|
|
76
|
+
|
|
77
|
+
Header behavior is:
|
|
78
|
+
|
|
79
|
+
- If `accessToken` or `tokenProvider` is configured, the client sends `Authorization: Bearer <token>`.
|
|
80
|
+
- If bearer auth is not configured, the client sends `X-API-Key` when `apiKey` is provided.
|
|
81
|
+
- If bearer auth is not configured and `clientId` is provided, the client also sends `X-Client-Id`.
|
|
82
|
+
- The client always sends `X-Actor-Id` for audit trails unless overridden.
|
|
83
|
+
|
|
84
|
+
That means this package wraps both operator/governed routes and selected external `/api/v1/*` routes. Pick the auth mode that matches the route family exposed by your deployment.
|
|
85
|
+
|
|
86
|
+
### Route Families
|
|
87
|
+
|
|
88
|
+
- Operator and governed routes are methods such as `createProjectCharter`, `getProjectBundle`, `importImplementationPacket`, and the workflow/portfolio/governance APIs.
|
|
89
|
+
- External routes are the methods prefixed with `getExternal*`, `listExternal*`, `downloadExternal*`, plus `startSessionGovernance`, `persistAssistantTurn`, `createExternalAudioRender`, and `getLatestMemoryProjection`.
|
|
90
|
+
|
|
91
|
+
If your deployment enforces bearer auth on operator routes, API-key-only construction will not work for those methods.
|
|
92
|
+
|
|
93
|
+
### Text and Binary Downloads
|
|
94
|
+
|
|
95
|
+
Some methods do not return plain JSON:
|
|
96
|
+
|
|
97
|
+
- Markdown download helpers return `{ text, contentType, fileName, headers }`.
|
|
98
|
+
- LOGA projection helpers return markdown plus governed projection metadata: `{ text, contentType, headers, logaContract, interactionContract, projectionType, projectionVersion, sourceTruth, sourceVersion, correlationId, refreshPolicy, generatedAt, provenance }`.
|
|
99
|
+
- Binary download helpers return `{ arrayBuffer, contentType, fileName }`.
|
|
100
|
+
|
|
101
|
+
Examples:
|
|
102
|
+
|
|
103
|
+
```js
|
|
104
|
+
import fs from 'node:fs';
|
|
105
|
+
|
|
106
|
+
const markdown = await client.downloadProjectCharterReportMarkdown(projectId);
|
|
107
|
+
console.log(markdown.fileName, markdown.contentType);
|
|
108
|
+
console.log(markdown.text);
|
|
109
|
+
|
|
110
|
+
const logaRoadmap = await client.getLogaProjectRoadmapProjection(projectId);
|
|
111
|
+
console.log(logaRoadmap.projectionType, logaRoadmap.projectionVersion);
|
|
112
|
+
console.log(logaRoadmap.provenance.sourceTruth, logaRoadmap.correlationId);
|
|
113
|
+
console.log(logaRoadmap.text);
|
|
114
|
+
|
|
115
|
+
const logaRoadmapItem = await client.getLogaRoadmapItemProjection(projectId, 'execute-scope-1');
|
|
116
|
+
console.log(logaRoadmapItem.projectionType, logaRoadmapItem.provenance.sourceVersion);
|
|
117
|
+
console.log(logaRoadmapItem.text);
|
|
118
|
+
|
|
119
|
+
const audio = await client.downloadExternalAudioRender(audioRenderRunId);
|
|
120
|
+
await fs.promises.writeFile('render.mp3', Buffer.from(audio.arrayBuffer));
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### LOGA Projection Contract
|
|
124
|
+
|
|
125
|
+
LOGA should use the `getLoga*Projection()` methods for runtime documents. These endpoints are governed AI Engine transformations: durable SQL state is normalized into typed projection models, transformed into versioned structured markdown, and served with provenance, refresh, and action metadata for LOGA to render.
|
|
126
|
+
|
|
127
|
+
The client exposes the transport metadata from response headers so LOGA can validate the contract before rendering:
|
|
128
|
+
|
|
129
|
+
- `logaContract`: currently `ai-engine-ui/v1`
|
|
130
|
+
- `interactionContract`: currently `loga-choreography/v1`
|
|
131
|
+
- `projectionType`: for example `operator.project_roadmap`
|
|
132
|
+
- `projectionWorkflow`: currently `loga-document-projection`
|
|
133
|
+
- `projectionVersion`
|
|
134
|
+
- `sourceTruth`: normally `sql`
|
|
135
|
+
- `sourceVersion`
|
|
136
|
+
- `correlationId`
|
|
137
|
+
- `refreshPolicy`
|
|
138
|
+
- `generatedAt`
|
|
139
|
+
- `provenance`: grouped copy of the source/projection/version fields
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
|
|
143
|
+
```js
|
|
144
|
+
import {
|
|
145
|
+
AIEngineClient,
|
|
146
|
+
LOGA_CONTRACT,
|
|
147
|
+
LOGA_INTERACTION_CONTRACT,
|
|
148
|
+
} from '@bpmsoftwaresolutions/ai-engine-client';
|
|
149
|
+
|
|
150
|
+
const client = AIEngineClient.fromEnv();
|
|
151
|
+
const projection = await client.getLogaProjectRoadmapProjection(projectId);
|
|
152
|
+
|
|
153
|
+
if (projection.logaContract !== LOGA_CONTRACT) {
|
|
154
|
+
throw new Error(`Unsupported LOGA contract: ${projection.logaContract}`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
console.log(projection.interactionContract === LOGA_INTERACTION_CONTRACT);
|
|
158
|
+
renderLogaMarkdown(projection.text, {
|
|
159
|
+
projectionType: projection.projectionType,
|
|
160
|
+
provenance: projection.provenance,
|
|
161
|
+
});
|
|
41
162
|
```
|
|
42
163
|
|
|
43
164
|
### fromEnv
|
|
44
165
|
|
|
45
166
|
```js
|
|
46
167
|
const client = AIEngineClient.fromEnv();
|
|
47
|
-
// Reads: AI_ENGINE_BASE_URL, AI_ENGINE_API_KEY,
|
|
168
|
+
// Reads: AI_ENGINE_BASE_URL, AI_ENGINE_ACCESS_TOKEN, AI_ENGINE_API_KEY,
|
|
169
|
+
// AI_ENGINE_CLIENT_ID, AI_ENGINE_ACTOR_ID
|
|
48
170
|
```
|
|
49
171
|
|
|
50
172
|
---
|
|
@@ -54,11 +176,156 @@ const client = AIEngineClient.fromEnv();
|
|
|
54
176
|
| Option | Type | Description |
|
|
55
177
|
|---|---|---|
|
|
56
178
|
| `baseUrl` | string | **Required.** Base URL of the AI Engine service. |
|
|
57
|
-
| `
|
|
179
|
+
| `accessToken` | string | Static bearer token sent as `Authorization`. |
|
|
180
|
+
| `tokenProvider` | function | Async callback that returns a bearer token string or `{ token }` / `{ accessToken }` per request. |
|
|
181
|
+
| `apiKey` | string | Compatibility API key sent as `X-API-Key` when bearer auth is not configured. |
|
|
182
|
+
| `clientId` | string | Compatibility client id sent as `X-Client-Id` when bearer auth is not configured. |
|
|
58
183
|
| `actorId` | string | Sent as `X-Actor-Id` for audit trails. Default: `sdk:npm-ai-engine-client`. |
|
|
59
184
|
| `fetchImpl` | function | Custom fetch (Node 18+ built-in used by default). |
|
|
60
185
|
| `timeoutMs` | number | Per-request timeout in ms. Default: 30000. |
|
|
61
186
|
|
|
187
|
+
### Token Provider
|
|
188
|
+
|
|
189
|
+
```js
|
|
190
|
+
const client = new AIEngineClient({
|
|
191
|
+
baseUrl: process.env.AI_ENGINE_BASE_URL,
|
|
192
|
+
tokenProvider: async () => {
|
|
193
|
+
const token = await acquireAccessToken();
|
|
194
|
+
return { token };
|
|
195
|
+
},
|
|
196
|
+
});
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### API Key Compatibility
|
|
200
|
+
|
|
201
|
+
```js
|
|
202
|
+
const client = new AIEngineClient({
|
|
203
|
+
baseUrl: process.env.AI_ENGINE_BASE_URL,
|
|
204
|
+
clientId: process.env.AI_ENGINE_CLIENT_ID,
|
|
205
|
+
apiKey: process.env.AI_ENGINE_API_KEY,
|
|
206
|
+
});
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
When `accessToken` or `tokenProvider` is present, the client prefers bearer auth. API key headers are only sent when bearer auth is not configured.
|
|
210
|
+
|
|
211
|
+
Some deployments accept a shared `X-API-Key` without `X-Client-Id`, while others require both `X-Client-Id` and `X-API-Key` for API-key auth. If your environment uses client-scoped API keys, provide both values.
|
|
212
|
+
|
|
213
|
+
### Project Chartering Contract
|
|
214
|
+
|
|
215
|
+
`createProjectCharter()` sends snake_case fields to `POST /api/projects/charter`. The method accepts:
|
|
216
|
+
|
|
217
|
+
- `projectName`, `objective`
|
|
218
|
+
- `businessContext`, `successCriteria`, `priority`
|
|
219
|
+
- `constraints`, `inScope`, `outOfScope`, `assumptions`
|
|
220
|
+
- `linkedWorkflows`, `linkedWorkflowSlug`
|
|
221
|
+
- `testingStrategy`, `initialContext`
|
|
222
|
+
- `requestedBy`
|
|
223
|
+
- `ensureTaskSurface`, `assignedTo`, `createAcceptanceSubtasks`
|
|
224
|
+
|
|
225
|
+
The response always includes the charter identifiers:
|
|
226
|
+
|
|
227
|
+
- `project_id`
|
|
228
|
+
- `workflow_id`
|
|
229
|
+
- `workflow_run_id`
|
|
230
|
+
- `implementation_packet_id`
|
|
231
|
+
- `implementation_packet_key`
|
|
232
|
+
- `status`
|
|
233
|
+
|
|
234
|
+
If `ensureTaskSurface` is left as `true`, the server also attempts to attach `task_surface` for the active roadmap item. That field is only available when the server can resolve an active implementation item from the project roadmap. If roadmap state is incomplete in the deployment, charter creation can still succeed while `task_surface` is unavailable.
|
|
235
|
+
|
|
236
|
+
Example:
|
|
237
|
+
|
|
238
|
+
```js
|
|
239
|
+
const charter = await client.createProjectCharter({
|
|
240
|
+
projectName: 'Loga Cognitive Interface Cockpit Delivery',
|
|
241
|
+
objective: 'Deliver the operator cockpit and playback experience.',
|
|
242
|
+
businessContext: 'Ground the first playback surface in convert-document-to-audio.',
|
|
243
|
+
linkedWorkflowSlug: 'convert-document-to-audio',
|
|
244
|
+
ensureTaskSurface: true,
|
|
245
|
+
createAcceptanceSubtasks: true,
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
console.log(charter.project_id);
|
|
249
|
+
console.log(charter.implementation_packet_key);
|
|
250
|
+
console.log(charter.task_surface?.active_item?.implementation_item_id ?? null);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Implementation Packet Import Contract
|
|
254
|
+
|
|
255
|
+
`importImplementationPacket(body)` expects the engine's `implementation_plan_packet` schema, not an arbitrary roadmap-shaped JSON document.
|
|
256
|
+
|
|
257
|
+
Minimum required top-level fields:
|
|
258
|
+
|
|
259
|
+
- `packetType`
|
|
260
|
+
- `packetVersion`
|
|
261
|
+
- `packetId`
|
|
262
|
+
- `title`
|
|
263
|
+
- `phases`
|
|
264
|
+
- `governance.requiredGates`
|
|
265
|
+
|
|
266
|
+
Minimum required fields for each item:
|
|
267
|
+
|
|
268
|
+
- `itemKey`
|
|
269
|
+
- `type`
|
|
270
|
+
- `description`
|
|
271
|
+
- `acceptanceChecks`
|
|
272
|
+
|
|
273
|
+
Allowed packet statuses include `draft`, `approved`, `blocked`, `completed`, and other governed packet lifecycle states. Custom statuses such as `draft-local-artifact` are not valid for import.
|
|
274
|
+
|
|
275
|
+
Minimal example:
|
|
276
|
+
|
|
277
|
+
```js
|
|
278
|
+
await client.importImplementationPacket({
|
|
279
|
+
packetType: 'implementation_plan_packet',
|
|
280
|
+
packetVersion: '1.0',
|
|
281
|
+
packetId: 'impl-loga-cockpit-v1',
|
|
282
|
+
title: 'Loga Cognitive Interface Cockpit Delivery',
|
|
283
|
+
status: 'draft',
|
|
284
|
+
governance: {
|
|
285
|
+
requiredGates: ['intent', 'structure', 'promotion'],
|
|
286
|
+
},
|
|
287
|
+
phases: [
|
|
288
|
+
{
|
|
289
|
+
phaseKey: 'phase_1_foundation',
|
|
290
|
+
title: 'Foundation',
|
|
291
|
+
items: [
|
|
292
|
+
{
|
|
293
|
+
itemKey: 'define-cockpit-surface',
|
|
294
|
+
type: 'ui_slice',
|
|
295
|
+
description: 'Define the first cockpit delivery slice.',
|
|
296
|
+
acceptanceChecks: [
|
|
297
|
+
{ text: 'The initial slice is explicitly bounded.' },
|
|
298
|
+
],
|
|
299
|
+
},
|
|
300
|
+
],
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
});
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
The package does not auto-convert nested human planning structures like `tasks` and `subtasks` into durable implementation-item tasks. To create persisted task records after import, use `ensureProjectRoadmapTaskSurface()` or `createImplementationTask()`.
|
|
307
|
+
|
|
308
|
+
A copyable example payload is packaged at `examples/implementation-plan-packet.minimal.json`.
|
|
309
|
+
|
|
310
|
+
### External Audio Render Example
|
|
311
|
+
|
|
312
|
+
```js
|
|
313
|
+
const render = await client.createExternalAudioRender({
|
|
314
|
+
text: '# Weekly Update\n\nThe platform migration is on track.',
|
|
315
|
+
voice: 'alloy',
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
const status = await client.getExternalAudioRender(render.audio_render_run_id);
|
|
319
|
+
const audio = await client.downloadExternalAudioRender(render.audio_render_run_id);
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### External Workflow Artifact Example
|
|
323
|
+
|
|
324
|
+
```js
|
|
325
|
+
const manifest = await client.listExternalWorkflowRunArtifacts('run-123');
|
|
326
|
+
const packet = await client.downloadExternalWorkflowRunArtifact('run-123', 'package_payload');
|
|
327
|
+
```
|
|
328
|
+
|
|
62
329
|
---
|
|
63
330
|
|
|
64
331
|
## Methods
|
|
@@ -67,6 +334,18 @@ const client = AIEngineClient.fromEnv();
|
|
|
67
334
|
| Method | Description |
|
|
68
335
|
|---|---|
|
|
69
336
|
| `ping()` | Health check + current workflow name/status. |
|
|
337
|
+
| `startSessionGovernance(body)` | Start a governed external session and return the `session_key` required for mutation-capable assistant turns. |
|
|
338
|
+
| `persistAssistantTurn(body)` | Persist an assistant turn to durable SQL through the API and return refreshed status projections. |
|
|
339
|
+
| `createExternalAudioRender({ text, voice, model, speed, file })` | Create a client-scoped external audio render using inline text or multipart upload. |
|
|
340
|
+
| `getExternalAudioRender(audioRenderRunId)` | Read external audio render status for the authenticated client. |
|
|
341
|
+
| `downloadExternalAudioRender(audioRenderRunId)` | Download the rendered MP3 artifact for the authenticated client. |
|
|
342
|
+
| `listExternalWorkflowRunArtifacts(workflowRunId)` | List signed external artifact descriptors for an authenticated external workflow run. |
|
|
343
|
+
| `downloadExternalWorkflowRunArtifact(workflowRunId, artifactType)` | Download one authenticated external workflow-run artifact by type. |
|
|
344
|
+
| `getExternalProjectStatus(projectId)` | API-key-compatible project status read through the external `/api/v1` boundary. |
|
|
345
|
+
| `getExternalProjectRoadmapSummary(projectId)` | API-key-compatible roadmap summary read through the external `/api/v1` boundary. |
|
|
346
|
+
| `getExternalProjectRoadmapActiveItem(projectId)` | API-key-compatible active roadmap item read through the external `/api/v1` boundary. |
|
|
347
|
+
| `listExternalProjectOpenTasks(projectId)` | API-key-compatible open task read through the external `/api/v1` boundary. |
|
|
348
|
+
| `getExternalProjectStatusBundle(projectId)` | One-call API-key-compatible bundle for project status, roadmap summary, active item, and open tasks. |
|
|
70
349
|
|
|
71
350
|
### Operator Status
|
|
72
351
|
| Method | Description |
|
|
@@ -75,7 +354,7 @@ const client = AIEngineClient.fromEnv();
|
|
|
75
354
|
| `currentArchitectureIntegrityStatus()` | Architecture integrity scan results. |
|
|
76
355
|
| `currentSecurityGovernanceStatus({ environment, topN })` | Security governance findings. |
|
|
77
356
|
| `currentCodebaseShapeStatus()` | Codebase shape analysis. |
|
|
78
|
-
| `getLatestMemoryProjection()` | Current SQL memory projection (startup hydration source). |
|
|
357
|
+
| `getLatestMemoryProjection()` | Current SQL memory projection (startup hydration source) through the general external `/api/v1/latest-memory-projection` route. |
|
|
79
358
|
| `currentProjectStatus({ projectId })` | Current project status projection from SQL memory. |
|
|
80
359
|
| `createDatabaseBackup({ databaseName, outputName, noWait })` | Start a database backup using server-owned Azure backup defaults. |
|
|
81
360
|
| `listDatabaseBackups({ prefix, limit })` | List recent backups from the server-owned backup storage location. |
|
|
@@ -93,6 +372,18 @@ Low-level compatibility methods:
|
|
|
93
372
|
| `listAzureSqlBacpacBackups({ storageAccount, container, ... })` | List BACPAC exports through the legacy infra-shaped operator API. |
|
|
94
373
|
| `listAzureSqlBacpacBackupOperations({ databaseName, resourceGroup, serverName, ... })` | List Azure SQL operations through the legacy infra-shaped operator API. |
|
|
95
374
|
|
|
375
|
+
### LOGA Projections
|
|
376
|
+
| Method | Endpoint | Description |
|
|
377
|
+
|---|---|---|
|
|
378
|
+
| `getLogaOperatorHomeProjection()` | `GET /api/operator/projections/home` | Governed operator home runtime markdown document. |
|
|
379
|
+
| `getLogaProjectCatalogProjection()` | `GET /api/operator/projections/project-catalog` | Governed project catalog runtime markdown document. |
|
|
380
|
+
| `getLogaProjectRoadmapProjection(projectId)` | `GET /api/operator/projections/projects/:projectId/roadmap.md` | Governed project roadmap runtime markdown document. |
|
|
381
|
+
| `getLogaRoadmapItemProjection(projectId, itemKey)` | `GET /api/operator/projections/projects/:projectId/roadmap/items/:itemKey` | Governed roadmap item runtime markdown document with item-level navigation, related documents, actions, and evidence. |
|
|
382
|
+
| `getLogaWorkflowRunProjection(workflowRunId)` | `GET /api/operator/projections/workflow-runs/:workflowRunId` | Governed workflow run runtime markdown document. |
|
|
383
|
+
| `getLogaEvidencePacketProjection(packetKey)` | `GET /api/operator/projections/evidence-packets/:packetKey` | Governed evidence packet runtime markdown document. |
|
|
384
|
+
|
|
385
|
+
These methods return markdown as `text` and expose projection headers as top-level fields plus `provenance`. LOGA should render the markdown emitted by AI Engine and use these metadata fields for contract validation, refresh behavior, evidence display, and action choreography.
|
|
386
|
+
|
|
96
387
|
### Retrieval Wrapper
|
|
97
388
|
| Method | Description |
|
|
98
389
|
|---|---|
|
|
@@ -169,6 +460,7 @@ Low-level compatibility methods:
|
|
|
169
460
|
| `getWorkflowRun(workflowRunId)` | Get run detail. |
|
|
170
461
|
| `listWorkflowArtifacts(workflowRunId)` | List run artifacts. |
|
|
171
462
|
| `getWorkflowRunSubstrate(workflowRunId)` | Get run substrate (sessions, turns). |
|
|
463
|
+
| `getWorkflowPlayback(workflowRunId)` | Get the playback-lite workflow timeline view derived from step runs and artifacts. |
|
|
172
464
|
| `resumeWorkflowRun(workflowRunId, body)` | Resume a paused run. |
|
|
173
465
|
| `listRecentInspectorRuns({ limit })` | List recent runs via inspector. |
|
|
174
466
|
| `inspectWorkflowRun(workflowRunId)` | Full inspector view of a run. |
|
|
@@ -219,6 +511,9 @@ Low-level compatibility methods:
|
|
|
219
511
|
| `completeImplementationTask(taskId, { completedBy })` | Mark complete. |
|
|
220
512
|
|
|
221
513
|
### Governed Implementation
|
|
514
|
+
|
|
515
|
+
Current package version: `1.0.0-beta.13`.
|
|
516
|
+
|
|
222
517
|
| Method | Description |
|
|
223
518
|
|---|---|
|
|
224
519
|
| `importImplementationPacket(body)` | Import an implementation packet. |
|
|
@@ -234,6 +529,26 @@ Low-level compatibility methods:
|
|
|
234
529
|
| `getWorkflowImplementationRoadmap(workflowId)` | Workflow-linked roadmap. |
|
|
235
530
|
| `getWorkflowResumeContext(workflowId)` | Resume context. |
|
|
236
531
|
|
|
532
|
+
Governed roadmap item mutations must carry claim context. Include the active
|
|
533
|
+
claim in the request body:
|
|
534
|
+
|
|
535
|
+
```js
|
|
536
|
+
await client.updateImplementationItemStatus(itemId, {
|
|
537
|
+
status: 'done',
|
|
538
|
+
status_reason: 'Evidence attached and gates passed.',
|
|
539
|
+
updated_by: 'operator:loga',
|
|
540
|
+
claimed_item_id: itemId,
|
|
541
|
+
agent_session_id: 'agent-session-id',
|
|
542
|
+
claim_workflow_run_id: 'workflow-run-id',
|
|
543
|
+
});
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
The same claim envelope applies to `addImplementationItemEvidence()`,
|
|
547
|
+
`addImplementationItemActivity()`, and `updateAcceptanceCheckStatus()`. If the
|
|
548
|
+
claim is missing, AI Engine returns `409` with `error:
|
|
549
|
+
missing_governed_claim`. If the claim exists but unresolved gates remain, AI
|
|
550
|
+
Engine returns the required gate and next action.
|
|
551
|
+
|
|
237
552
|
### Skills
|
|
238
553
|
| Method | Description |
|
|
239
554
|
|---|---|
|
|
@@ -375,4 +690,4 @@ Low-level compatibility methods:
|
|
|
375
690
|
|
|
376
691
|
The package talks to the AI Engine web service. All routes listed above are registered unconditionally in the Flask app and served by the deployed Azure Container App.
|
|
377
692
|
|
|
378
|
-
Operator routes
|
|
693
|
+
Operator and governed routes generally expect bearer-token authorization. The package also wraps selected external `/api/v1/*` routes that support migration-oriented auth modes such as bearer or API-key compatibility, depending on deployment configuration.
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
{
|
|
2
|
+
"packetType": "implementation_plan_packet",
|
|
3
|
+
"packetVersion": "1.0",
|
|
4
|
+
"packetId": "impl-example-governed-slice-v1",
|
|
5
|
+
"title": "Example Governed Slice Implementation Packet",
|
|
6
|
+
"status": "draft",
|
|
7
|
+
"createdBy": {
|
|
8
|
+
"kind": "operator",
|
|
9
|
+
"name": "sdk-example"
|
|
10
|
+
},
|
|
11
|
+
"sourceModel": {
|
|
12
|
+
"producer": "ai-engine-client-readme",
|
|
13
|
+
"purpose": "minimal_import_example"
|
|
14
|
+
},
|
|
15
|
+
"system": {
|
|
16
|
+
"slug": "example-governed-slice",
|
|
17
|
+
"domain": "project-chartering"
|
|
18
|
+
},
|
|
19
|
+
"scope": {
|
|
20
|
+
"productArea": "example-governed-slice",
|
|
21
|
+
"workflowIntent": "Demonstrate the minimum durable implementation packet contract.",
|
|
22
|
+
"targetSurface": "governed-project-execution",
|
|
23
|
+
"inScope": [
|
|
24
|
+
"first delivery slice"
|
|
25
|
+
],
|
|
26
|
+
"outOfScope": [
|
|
27
|
+
"full product rollout"
|
|
28
|
+
]
|
|
29
|
+
},
|
|
30
|
+
"northStar": {
|
|
31
|
+
"summary": "Define a small but valid governed implementation packet."
|
|
32
|
+
},
|
|
33
|
+
"governance": {
|
|
34
|
+
"requiredGates": [
|
|
35
|
+
"intent",
|
|
36
|
+
"structure",
|
|
37
|
+
"promotion"
|
|
38
|
+
],
|
|
39
|
+
"reviewRequired": true,
|
|
40
|
+
"evidenceRequiredForCompletion": true
|
|
41
|
+
},
|
|
42
|
+
"completionPolicy": {
|
|
43
|
+
"requiresEvidence": true,
|
|
44
|
+
"requiresReview": true
|
|
45
|
+
},
|
|
46
|
+
"phases": [
|
|
47
|
+
{
|
|
48
|
+
"phaseKey": "phase_1_foundation",
|
|
49
|
+
"title": "Foundation",
|
|
50
|
+
"items": [
|
|
51
|
+
{
|
|
52
|
+
"itemKey": "define-first-slice",
|
|
53
|
+
"type": "delivery_slice",
|
|
54
|
+
"priority": "high",
|
|
55
|
+
"title": "Define first governed slice",
|
|
56
|
+
"description": "Define the first durable delivery slice and bind its acceptance criteria.",
|
|
57
|
+
"status": "not_started",
|
|
58
|
+
"dependsOn": [],
|
|
59
|
+
"artifactsExpected": [
|
|
60
|
+
"delivery-slice-definition.md"
|
|
61
|
+
],
|
|
62
|
+
"acceptanceChecks": [
|
|
63
|
+
{
|
|
64
|
+
"text": "The slice is explicitly bounded.",
|
|
65
|
+
"status": "not_run"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"text": "The slice can be attached to durable task surfaces.",
|
|
69
|
+
"status": "not_run"
|
|
70
|
+
}
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
}
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bpmsoftwaresolutions/ai-engine-client",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Thin npm client for the AI Engine operator and retrieval APIs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"test": "node --test"
|
|
9
|
+
},
|
|
7
10
|
"exports": {
|
|
8
11
|
".": "./src/index.js"
|
|
9
12
|
},
|
|
10
13
|
"files": [
|
|
11
14
|
"src",
|
|
12
|
-
"README.md"
|
|
15
|
+
"README.md",
|
|
16
|
+
"examples"
|
|
13
17
|
],
|
|
14
18
|
"engines": {
|
|
15
19
|
"node": ">=18"
|
|
@@ -25,4 +29,4 @@
|
|
|
25
29
|
"publishConfig": {
|
|
26
30
|
"access": "public"
|
|
27
31
|
}
|
|
28
|
-
}
|
|
32
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
const DEFAULT_TIMEOUT_MS = 30000;
|
|
2
2
|
|
|
3
|
+
export const LOGA_CONTRACT = 'ai-engine-ui/v1';
|
|
4
|
+
export const LOGA_INTERACTION_CONTRACT = 'loga-choreography/v1';
|
|
5
|
+
export const LOGA_NAVIGATION_CONTRACT = 'loga-navigation/v1';
|
|
6
|
+
export const LOGA_PROJECTION_WORKFLOW = 'loga-document-projection';
|
|
7
|
+
|
|
3
8
|
function trimTrailingSlash(value) {
|
|
4
9
|
return String(value || '').replace(/\/+$/, '');
|
|
5
10
|
}
|
|
@@ -28,11 +33,74 @@ function parseContentDispositionFilename(headerValue) {
|
|
|
28
33
|
return plainMatch ? plainMatch[1].trim() : null;
|
|
29
34
|
}
|
|
30
35
|
|
|
36
|
+
function readResponseHeader(headers, name) {
|
|
37
|
+
if (!headers || typeof headers.get !== 'function') return null;
|
|
38
|
+
return headers.get(name) || headers.get(name.toLowerCase()) || null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function extractLogaProjectionMetadata(headers) {
|
|
42
|
+
const projectionWorkflow = readResponseHeader(headers, 'x-projection-workflow');
|
|
43
|
+
const projectionVersion = readResponseHeader(headers, 'x-projection-version');
|
|
44
|
+
const sourceVersion = readResponseHeader(headers, 'x-source-version');
|
|
45
|
+
const correlationId = readResponseHeader(headers, 'x-correlation-id');
|
|
46
|
+
const logaContract = readResponseHeader(headers, 'x-loga-contract');
|
|
47
|
+
const navigationContract = readResponseHeader(headers, 'x-navigation-contract');
|
|
48
|
+
const projectionType = readResponseHeader(headers, 'x-projection-type');
|
|
49
|
+
const sourceTruth = readResponseHeader(headers, 'x-source-truth');
|
|
50
|
+
const refreshPolicy = readResponseHeader(headers, 'x-refresh-policy');
|
|
51
|
+
const generatedAt = readResponseHeader(headers, 'x-generated-at');
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
logaContract,
|
|
55
|
+
navigationContract,
|
|
56
|
+
interactionContract: LOGA_INTERACTION_CONTRACT,
|
|
57
|
+
projectionType,
|
|
58
|
+
projectionWorkflow,
|
|
59
|
+
projectionVersion,
|
|
60
|
+
sourceTruth,
|
|
61
|
+
sourceVersion,
|
|
62
|
+
correlationId,
|
|
63
|
+
refreshPolicy,
|
|
64
|
+
generatedAt,
|
|
65
|
+
provenance: {
|
|
66
|
+
sourceTruth,
|
|
67
|
+
sourceVersion,
|
|
68
|
+
projectionVersion,
|
|
69
|
+
projectionWorkflow,
|
|
70
|
+
correlationId,
|
|
71
|
+
generatedAt,
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function isFormDataBody(value) {
|
|
77
|
+
return typeof FormData !== 'undefined' && value instanceof FormData;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function isBinaryBody(value) {
|
|
81
|
+
return (
|
|
82
|
+
value instanceof ArrayBuffer
|
|
83
|
+
|| ArrayBuffer.isView(value)
|
|
84
|
+
|| (typeof Blob !== 'undefined' && value instanceof Blob)
|
|
85
|
+
|| value instanceof URLSearchParams
|
|
86
|
+
|| typeof value === 'string'
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isJsonBody(value) {
|
|
91
|
+
if (value === undefined || value === null) return false;
|
|
92
|
+
if (isFormDataBody(value) || isBinaryBody(value)) return false;
|
|
93
|
+
return typeof value === 'object';
|
|
94
|
+
}
|
|
95
|
+
|
|
31
96
|
export class AIEngineClient {
|
|
32
|
-
constructor({ baseUrl, apiKey, actorId, fetchImpl, timeoutMs } = {}) {
|
|
97
|
+
constructor({ baseUrl, accessToken, tokenProvider, apiKey, clientId, actorId, fetchImpl, timeoutMs } = {}) {
|
|
33
98
|
if (!baseUrl) throw new Error('baseUrl is required.');
|
|
34
99
|
this.baseUrl = trimTrailingSlash(baseUrl);
|
|
100
|
+
this.accessToken = accessToken || null;
|
|
101
|
+
this.tokenProvider = tokenProvider || null;
|
|
35
102
|
this.apiKey = apiKey || null;
|
|
103
|
+
this.clientId = clientId || null;
|
|
36
104
|
this.actorId = actorId || 'sdk:npm-ai-engine-client';
|
|
37
105
|
this.fetchImpl = fetchImpl || globalThis.fetch;
|
|
38
106
|
this.timeoutMs = timeoutMs || DEFAULT_TIMEOUT_MS;
|
|
@@ -44,7 +112,10 @@ export class AIEngineClient {
|
|
|
44
112
|
static fromEnv(options = {}) {
|
|
45
113
|
return new AIEngineClient({
|
|
46
114
|
baseUrl: options.baseUrl || process.env.AI_ENGINE_BASE_URL || process.env.AI_ENGINE_API_BASE_URL,
|
|
115
|
+
accessToken: options.accessToken || process.env.AI_ENGINE_ACCESS_TOKEN || null,
|
|
116
|
+
tokenProvider: options.tokenProvider || null,
|
|
47
117
|
apiKey: options.apiKey || process.env.AI_ENGINE_API_KEY || null,
|
|
118
|
+
clientId: options.clientId || process.env.AI_ENGINE_CLIENT_ID || null,
|
|
48
119
|
actorId: options.actorId || process.env.AI_ENGINE_ACTOR_ID || 'sdk:npm-ai-engine-client',
|
|
49
120
|
});
|
|
50
121
|
}
|
|
@@ -85,7 +156,7 @@ export class AIEngineClient {
|
|
|
85
156
|
}
|
|
86
157
|
|
|
87
158
|
async getLatestMemoryProjection() {
|
|
88
|
-
return this._request('/api/
|
|
159
|
+
return this._request('/api/v1/latest-memory-projection');
|
|
89
160
|
}
|
|
90
161
|
|
|
91
162
|
async currentProjectStatus({ projectId } = {}) {
|
|
@@ -209,6 +280,65 @@ export class AIEngineClient {
|
|
|
209
280
|
return this._request('/api/dashboard');
|
|
210
281
|
}
|
|
211
282
|
|
|
283
|
+
async startSessionGovernance(body) {
|
|
284
|
+
return this._request('/api/v1/session-governance/startup', { method: 'POST', body });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async persistAssistantTurn(body) {
|
|
288
|
+
return this._request('/api/v1/assistant-turns', { method: 'POST', body });
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async getExternalProjectStatus(projectId) {
|
|
292
|
+
return this._request(`/api/v1/projects/${projectId}/status`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async getExternalProjectRoadmapSummary(projectId) {
|
|
296
|
+
return this._request(`/api/v1/projects/${projectId}/implementation-roadmap/summary`);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
async getExternalProjectRoadmapActiveItem(projectId) {
|
|
300
|
+
return this._request(`/api/v1/projects/${projectId}/implementation-roadmap/active-item`);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async listExternalProjectOpenTasks(projectId) {
|
|
304
|
+
return this._request(`/api/v1/projects/${projectId}/open-tasks`);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
async getExternalProjectStatusBundle(projectId) {
|
|
308
|
+
return this._request(`/api/v1/projects/${projectId}/status-bundle`);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
async createExternalAudioRender({ text, voice, model, speed, file } = {}) {
|
|
312
|
+
if (file) {
|
|
313
|
+
const form = new FormData();
|
|
314
|
+
form.append('file', file);
|
|
315
|
+
if (voice !== undefined) form.append('voice', String(voice));
|
|
316
|
+
if (model !== undefined) form.append('model', String(model));
|
|
317
|
+
if (speed !== undefined) form.append('speed', String(speed));
|
|
318
|
+
return this._request('/api/v1/audio-renders', { method: 'POST', body: form });
|
|
319
|
+
}
|
|
320
|
+
return this._request('/api/v1/audio-renders', {
|
|
321
|
+
method: 'POST',
|
|
322
|
+
body: { text, voice, model, speed },
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async getExternalAudioRender(audioRenderRunId) {
|
|
327
|
+
return this._request(`/api/v1/audio-renders/${audioRenderRunId}`);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
async downloadExternalAudioRender(audioRenderRunId) {
|
|
331
|
+
return this._requestBinary(`/api/v1/audio-renders/${audioRenderRunId}/download`);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
async listExternalWorkflowRunArtifacts(workflowRunId) {
|
|
335
|
+
return this._request(`/api/v1/runs/${workflowRunId}/artifacts`);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async downloadExternalWorkflowRunArtifact(workflowRunId, artifactType) {
|
|
339
|
+
return this._requestBinary(`/api/v1/runs/${workflowRunId}/artifacts/${artifactType}/download`);
|
|
340
|
+
}
|
|
341
|
+
|
|
212
342
|
// ─── Retrieval Wrapper ─────────────────────────────────────────────────────
|
|
213
343
|
|
|
214
344
|
async getCommandCard({ commandKey, alias, intentText, requestedBy } = {}) {
|
|
@@ -505,6 +635,10 @@ export class AIEngineClient {
|
|
|
505
635
|
return this._request(`/api/workflow-runs/${workflowRunId}/substrate`);
|
|
506
636
|
}
|
|
507
637
|
|
|
638
|
+
async getWorkflowPlayback(workflowRunId) {
|
|
639
|
+
return this._request(`/api/operator/workflow-runs/${workflowRunId}/playback`);
|
|
640
|
+
}
|
|
641
|
+
|
|
508
642
|
async resumeWorkflowRun(workflowRunId, body = {}) {
|
|
509
643
|
return this._request(`/api/workflow-runs/${workflowRunId}/resume`, { method: 'POST', body });
|
|
510
644
|
}
|
|
@@ -620,6 +754,32 @@ export class AIEngineClient {
|
|
|
620
754
|
return this._request(`/api/operator/projects/${projectId}/bundle`);
|
|
621
755
|
}
|
|
622
756
|
|
|
757
|
+
// LOGA projections are governed runtime documents, not static reports.
|
|
758
|
+
|
|
759
|
+
async getLogaOperatorHomeProjection() {
|
|
760
|
+
return this._requestLogaProjection('/api/operator/projections/home');
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
async getLogaProjectCatalogProjection() {
|
|
764
|
+
return this._requestLogaProjection('/api/operator/projections/project-catalog');
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
async getLogaProjectRoadmapProjection(projectId) {
|
|
768
|
+
return this._requestLogaProjection(`/api/operator/projections/projects/${projectId}/roadmap.md`);
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
async getLogaRoadmapItemProjection(projectId, itemKey) {
|
|
772
|
+
return this._requestLogaProjection(`/api/operator/projections/projects/${projectId}/roadmap/items/${itemKey}`);
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
async getLogaWorkflowRunProjection(workflowRunId) {
|
|
776
|
+
return this._requestLogaProjection(`/api/operator/projections/workflow-runs/${workflowRunId}`);
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async getLogaEvidencePacketProjection(packetKey) {
|
|
780
|
+
return this._requestLogaProjection(`/api/operator/projections/evidence-packets/${packetKey}`);
|
|
781
|
+
}
|
|
782
|
+
|
|
623
783
|
// ─── Roadmaps ──────────────────────────────────────────────────────────────
|
|
624
784
|
|
|
625
785
|
async listProjectRoadmaps({ includeInactive } = {}) {
|
|
@@ -1217,23 +1377,177 @@ export class AIEngineClient {
|
|
|
1217
1377
|
return this._request(`/api/benchmarks/reasoners/runs/${benchmarkRunId}`);
|
|
1218
1378
|
}
|
|
1219
1379
|
|
|
1380
|
+
// ─── Commit Governance ────────────────────────────────────────────────────
|
|
1381
|
+
|
|
1382
|
+
async evaluateCommitGovernance({
|
|
1383
|
+
claimId,
|
|
1384
|
+
contextSessionId,
|
|
1385
|
+
agentId,
|
|
1386
|
+
intentId,
|
|
1387
|
+
changedFiles,
|
|
1388
|
+
declaredScopeFiles,
|
|
1389
|
+
diffSummary,
|
|
1390
|
+
branch,
|
|
1391
|
+
headSha,
|
|
1392
|
+
} = {}) {
|
|
1393
|
+
return this._request('/api/commit-governance/evaluate', {
|
|
1394
|
+
method: 'POST',
|
|
1395
|
+
body: {
|
|
1396
|
+
claim_id: claimId,
|
|
1397
|
+
context_session_id: contextSessionId,
|
|
1398
|
+
agent_id: agentId,
|
|
1399
|
+
intent_id: intentId,
|
|
1400
|
+
changed_files: changedFiles,
|
|
1401
|
+
declared_scope_files: declaredScopeFiles,
|
|
1402
|
+
diff_summary: diffSummary,
|
|
1403
|
+
branch,
|
|
1404
|
+
head_sha: headSha,
|
|
1405
|
+
},
|
|
1406
|
+
});
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
async getCommitGovernanceEvaluation(evaluationId) {
|
|
1410
|
+
return this._request(`/api/commit-governance/evaluations/${encodeURIComponent(evaluationId)}`);
|
|
1411
|
+
}
|
|
1412
|
+
|
|
1413
|
+
async listCommitGovernanceEvaluationsByClaim(claimId, { limit } = {}) {
|
|
1414
|
+
return this._request(`/api/commit-governance/claims/${encodeURIComponent(claimId)}/evaluations`, {
|
|
1415
|
+
query: { limit },
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// ─── Context Session Orientation ──────────────────────────────────────────
|
|
1420
|
+
|
|
1421
|
+
async openContextSession({ agentId, runtimeSessionId, intentId, executionPurpose, notes } = {}) {
|
|
1422
|
+
return this._request('/api/context-session/open', {
|
|
1423
|
+
method: 'POST',
|
|
1424
|
+
body: {
|
|
1425
|
+
agent_id: agentId,
|
|
1426
|
+
runtime_session_id: runtimeSessionId,
|
|
1427
|
+
intent_id: intentId,
|
|
1428
|
+
execution_purpose: executionPurpose,
|
|
1429
|
+
notes,
|
|
1430
|
+
},
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
async getOrientationWindow(contextSessionId) {
|
|
1435
|
+
return this._request(`/api/context-session/${encodeURIComponent(contextSessionId)}/window`);
|
|
1436
|
+
}
|
|
1437
|
+
|
|
1438
|
+
async acknowledgeReminder(contextSessionId, { reminderId, agentSignature, understandingSummary } = {}) {
|
|
1439
|
+
return this._request(`/api/context-session/${encodeURIComponent(contextSessionId)}/acknowledge`, {
|
|
1440
|
+
method: 'POST',
|
|
1441
|
+
body: {
|
|
1442
|
+
reminder_id: reminderId,
|
|
1443
|
+
agent_signature: agentSignature,
|
|
1444
|
+
understanding_summary: understandingSummary,
|
|
1445
|
+
},
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
|
|
1449
|
+
async completeOrientation(contextSessionId) {
|
|
1450
|
+
return this._request(`/api/context-session/${encodeURIComponent(contextSessionId)}/complete`, {
|
|
1451
|
+
method: 'POST',
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
async lockContextSessionClaim(contextSessionId) {
|
|
1456
|
+
return this._request(`/api/context-session/${encodeURIComponent(contextSessionId)}/lock-claim`, {
|
|
1457
|
+
method: 'POST',
|
|
1458
|
+
});
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1461
|
+
async getContextSessionGateStatus(contextSessionId) {
|
|
1462
|
+
return this._request(`/api/context-session/${encodeURIComponent(contextSessionId)}/gate-status`);
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
async conductOrientation({
|
|
1466
|
+
agentId,
|
|
1467
|
+
runtimeSessionId,
|
|
1468
|
+
intentId,
|
|
1469
|
+
executionPurpose,
|
|
1470
|
+
agentSignature,
|
|
1471
|
+
understandingSummary,
|
|
1472
|
+
lockClaim = true,
|
|
1473
|
+
} = {}) {
|
|
1474
|
+
const { context_session, reminders } = await this.openContextSession({
|
|
1475
|
+
agentId,
|
|
1476
|
+
runtimeSessionId,
|
|
1477
|
+
intentId,
|
|
1478
|
+
executionPurpose,
|
|
1479
|
+
});
|
|
1480
|
+
const sessionId = context_session.context_session_id;
|
|
1481
|
+
|
|
1482
|
+
const required = (reminders || []).filter(r => r.severity === 'required');
|
|
1483
|
+
let acknowledgedCount = 0;
|
|
1484
|
+
for (const reminder of required) {
|
|
1485
|
+
await this.acknowledgeReminder(sessionId, {
|
|
1486
|
+
reminderId: reminder.reminder_id,
|
|
1487
|
+
agentSignature,
|
|
1488
|
+
understandingSummary,
|
|
1489
|
+
});
|
|
1490
|
+
acknowledgedCount++;
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
const completion = await this.completeOrientation(sessionId);
|
|
1494
|
+
|
|
1495
|
+
let claimLockResult = null;
|
|
1496
|
+
if (lockClaim) {
|
|
1497
|
+
claimLockResult = await this.lockContextSessionClaim(sessionId);
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
return {
|
|
1501
|
+
context_session_id: sessionId,
|
|
1502
|
+
intent_id: intentId,
|
|
1503
|
+
context_session: claimLockResult?.context_session || completion.context_session,
|
|
1504
|
+
gate_result: claimLockResult?.gate_result || completion.gate_result,
|
|
1505
|
+
acknowledged_count: acknowledgedCount,
|
|
1506
|
+
claim_locked: lockClaim && !!claimLockResult,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1220
1510
|
// ─── Core HTTP ─────────────────────────────────────────────────────────────
|
|
1221
1511
|
|
|
1512
|
+
async _resolveAccessToken() {
|
|
1513
|
+
if (typeof this.tokenProvider === 'function') {
|
|
1514
|
+
const provided = await this.tokenProvider();
|
|
1515
|
+
if (!provided) return null;
|
|
1516
|
+
if (typeof provided === 'string') return provided;
|
|
1517
|
+
if (typeof provided === 'object' && typeof provided.token === 'string') return provided.token;
|
|
1518
|
+
if (typeof provided === 'object' && typeof provided.accessToken === 'string') return provided.accessToken;
|
|
1519
|
+
throw new Error('tokenProvider must return a token string or an object with a token or accessToken field.');
|
|
1520
|
+
}
|
|
1521
|
+
return this.accessToken;
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
async _buildHeaders({ headers, body, accept }) {
|
|
1525
|
+
const token = await this._resolveAccessToken();
|
|
1526
|
+
const resolvedHeaders = {
|
|
1527
|
+
accept: accept || 'application/json',
|
|
1528
|
+
'x-actor-id': this.actorId,
|
|
1529
|
+
...(isJsonBody(body) ? { 'content-type': 'application/json' } : {}),
|
|
1530
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
1531
|
+
...(!token && this.clientId ? { 'x-client-id': this.clientId } : {}),
|
|
1532
|
+
...(!token && this.apiKey ? { 'x-api-key': this.apiKey } : {}),
|
|
1533
|
+
...headers,
|
|
1534
|
+
};
|
|
1535
|
+
for (const [key, value] of Object.entries(resolvedHeaders)) {
|
|
1536
|
+
if (value === undefined) delete resolvedHeaders[key];
|
|
1537
|
+
}
|
|
1538
|
+
return resolvedHeaders;
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1222
1541
|
async _request(path, { method = 'GET', query, headers, body } = {}) {
|
|
1223
1542
|
const url = appendQuery(`${this.baseUrl}${path}`, query);
|
|
1224
1543
|
const controller = new AbortController();
|
|
1225
1544
|
const timeoutHandle = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1226
1545
|
try {
|
|
1546
|
+
const resolvedHeaders = await this._buildHeaders({ headers, body, accept: 'application/json' });
|
|
1227
1547
|
const response = await this.fetchImpl(url, {
|
|
1228
1548
|
method,
|
|
1229
|
-
headers:
|
|
1230
|
-
|
|
1231
|
-
'content-type': body ? 'application/json' : undefined,
|
|
1232
|
-
'x-actor-id': this.actorId,
|
|
1233
|
-
authorization: this.apiKey ? `Bearer ${this.apiKey}` : undefined,
|
|
1234
|
-
...headers,
|
|
1235
|
-
},
|
|
1236
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
1549
|
+
headers: resolvedHeaders,
|
|
1550
|
+
body: isJsonBody(body) ? JSON.stringify(body) : body,
|
|
1237
1551
|
signal: controller.signal,
|
|
1238
1552
|
});
|
|
1239
1553
|
const payload = await readJson(response);
|
|
@@ -1254,16 +1568,15 @@ export class AIEngineClient {
|
|
|
1254
1568
|
const controller = new AbortController();
|
|
1255
1569
|
const timeoutHandle = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1256
1570
|
try {
|
|
1571
|
+
const resolvedHeaders = await this._buildHeaders({
|
|
1572
|
+
headers,
|
|
1573
|
+
body,
|
|
1574
|
+
accept: 'text/markdown, text/plain;q=0.9, application/json;q=0.8',
|
|
1575
|
+
});
|
|
1257
1576
|
const response = await this.fetchImpl(url, {
|
|
1258
1577
|
method,
|
|
1259
|
-
headers:
|
|
1260
|
-
|
|
1261
|
-
'content-type': body ? 'application/json' : undefined,
|
|
1262
|
-
'x-actor-id': this.actorId,
|
|
1263
|
-
authorization: this.apiKey ? `Bearer ${this.apiKey}` : undefined,
|
|
1264
|
-
...headers,
|
|
1265
|
-
},
|
|
1266
|
-
body: body ? JSON.stringify(body) : undefined,
|
|
1578
|
+
headers: resolvedHeaders,
|
|
1579
|
+
body: isJsonBody(body) ? JSON.stringify(body) : body,
|
|
1267
1580
|
signal: controller.signal,
|
|
1268
1581
|
});
|
|
1269
1582
|
const contentType = response.headers.get('content-type') || '';
|
|
@@ -1281,6 +1594,53 @@ export class AIEngineClient {
|
|
|
1281
1594
|
text: await response.text(),
|
|
1282
1595
|
contentType,
|
|
1283
1596
|
fileName: parseContentDispositionFilename(contentDisposition),
|
|
1597
|
+
headers: Object.fromEntries(response.headers.entries()),
|
|
1598
|
+
};
|
|
1599
|
+
} finally {
|
|
1600
|
+
clearTimeout(timeoutHandle);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
async _requestLogaProjection(path, options = {}) {
|
|
1605
|
+
const result = await this._requestText(path, options);
|
|
1606
|
+
return {
|
|
1607
|
+
...result,
|
|
1608
|
+
...extractLogaProjectionMetadata(new Headers(result.headers || {})),
|
|
1609
|
+
};
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
async _requestBinary(path, { method = 'GET', query, headers, body } = {}) {
|
|
1613
|
+
const url = appendQuery(`${this.baseUrl}${path}`, query);
|
|
1614
|
+
const controller = new AbortController();
|
|
1615
|
+
const timeoutHandle = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
1616
|
+
try {
|
|
1617
|
+
const resolvedHeaders = await this._buildHeaders({
|
|
1618
|
+
headers,
|
|
1619
|
+
body,
|
|
1620
|
+
accept: 'audio/mpeg, application/octet-stream;q=0.9, application/json;q=0.8',
|
|
1621
|
+
});
|
|
1622
|
+
const response = await this.fetchImpl(url, {
|
|
1623
|
+
method,
|
|
1624
|
+
headers: resolvedHeaders,
|
|
1625
|
+
body: isJsonBody(body) ? JSON.stringify(body) : body,
|
|
1626
|
+
signal: controller.signal,
|
|
1627
|
+
});
|
|
1628
|
+
const contentType = response.headers.get('content-type') || '';
|
|
1629
|
+
const contentDisposition = response.headers.get('content-disposition') || '';
|
|
1630
|
+
if (!response.ok) {
|
|
1631
|
+
const payload = contentType.includes('application/json')
|
|
1632
|
+
? await response.json()
|
|
1633
|
+
: { message: await response.text() };
|
|
1634
|
+
const error = new Error(payload?.message || payload?.error || `Request failed with status ${response.status}.`);
|
|
1635
|
+
error.status = response.status;
|
|
1636
|
+
error.payload = payload;
|
|
1637
|
+
throw error;
|
|
1638
|
+
}
|
|
1639
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
1640
|
+
return {
|
|
1641
|
+
arrayBuffer,
|
|
1642
|
+
contentType,
|
|
1643
|
+
fileName: parseContentDispositionFilename(contentDisposition),
|
|
1284
1644
|
};
|
|
1285
1645
|
} finally {
|
|
1286
1646
|
clearTimeout(timeoutHandle);
|