@openwop/openwop 1.9.0 → 2.0.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 +64 -117
- package/dist/client.d.ts +131 -245
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +233 -440
- package/dist/client.js.map +1 -1
- package/dist/cost-attribution.d.ts +2 -2
- package/dist/cost-attribution.js +2 -2
- package/dist/envelope-directive.d.ts +1 -1
- package/dist/envelope-directive.js +1 -1
- package/dist/event-helpers.js +1 -1
- package/dist/event-helpers.js.map +1 -1
- package/dist/generated.d.ts +17 -0
- package/dist/generated.d.ts.map +1 -0
- package/dist/generated.js +311 -0
- package/dist/generated.js.map +1 -0
- package/dist/index.d.ts +16 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +26 -56
- package/dist/index.js.map +1 -1
- package/dist/run-helpers.d.ts +20 -21
- package/dist/run-helpers.d.ts.map +1 -1
- package/dist/run-helpers.js +23 -72
- package/dist/run-helpers.js.map +1 -1
- package/dist/sse.d.ts +33 -15
- package/dist/sse.d.ts.map +1 -1
- package/dist/sse.js +28 -30
- package/dist/sse.js.map +1 -1
- package/dist/types.d.ts +253 -559
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/webhook-header-families.d.ts +22 -13
- package/dist/webhook-header-families.d.ts.map +1 -1
- package/dist/webhook-header-families.js +30 -27
- package/dist/webhook-header-families.js.map +1 -1
- package/dist/webhook-helpers.browser.d.ts +15 -29
- package/dist/webhook-helpers.browser.d.ts.map +1 -1
- package/dist/webhook-helpers.browser.js +16 -31
- package/dist/webhook-helpers.browser.js.map +1 -1
- package/dist/webhook-helpers.d.ts +41 -41
- package/dist/webhook-helpers.d.ts.map +1 -1
- package/dist/webhook-helpers.js +40 -47
- package/dist/webhook-helpers.js.map +1 -1
- package/package.json +6 -4
- package/src/client.ts +255 -454
- package/src/cost-attribution.ts +2 -2
- package/src/envelope-directive.ts +1 -1
- package/src/event-helpers.ts +1 -1
- package/src/generated.ts +322 -0
- package/src/index.ts +78 -110
- package/src/run-helpers.ts +27 -85
- package/src/sse.ts +63 -42
- package/src/types.ts +268 -603
- package/src/webhook-header-families.ts +41 -29
- package/src/webhook-helpers.browser.ts +23 -32
- package/src/webhook-helpers.ts +67 -53
- package/dist/registry-helpers.d.ts +0 -118
- package/dist/registry-helpers.d.ts.map +0 -1
- package/dist/registry-helpers.js +0 -82
- package/dist/registry-helpers.js.map +0 -1
- package/src/registry-helpers.ts +0 -173
package/dist/client.js
CHANGED
|
@@ -1,19 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* OpenwopClient — typed HTTP client for the
|
|
2
|
+
* OpenwopClient — typed HTTP client for the OpenWOP v2 REST surface.
|
|
3
3
|
*
|
|
4
|
-
* Hand-authored. Each method maps 1:1 to
|
|
5
|
-
*
|
|
4
|
+
* Hand-authored. Each method maps 1:1 to an operation in
|
|
5
|
+
* `spec/v2/path-manifest.json` (generated from `api/v2/openapi.yaml`):
|
|
6
|
+
* bare origin, unversioned path keys, negotiation by the `OpenWOP-Version`
|
|
7
|
+
* request header (RFC 0172 §A). Request/response types live in ./types.ts;
|
|
8
|
+
* the error-code union in ./generated.ts.
|
|
6
9
|
*
|
|
7
|
-
* Auth: a single bearer-style API key, supplied at construction.
|
|
8
|
-
* ../../auth.md for credential format.
|
|
10
|
+
* Auth: a single bearer-style API key, supplied at construction.
|
|
9
11
|
*/
|
|
10
|
-
import { streamEvents } from './sse.js';
|
|
12
|
+
import { streamEvents, streamHostEvents } from './sse.js';
|
|
11
13
|
import { WopError, } from './types.js';
|
|
14
|
+
/** The protocol major this SDK implements; the default for {@link OpenwopClientOptions.major}. */
|
|
15
|
+
export const SDK_PROTOCOL_MAJOR = 2;
|
|
16
|
+
/** Renders the `OpenWOP-Version` request value for a major: `<major>.0` (the OpenAPI grammar is `<major>.<minor>`). */
|
|
17
|
+
export function protocolVersionHeader(major) {
|
|
18
|
+
if (!Number.isInteger(major) || major < 0) {
|
|
19
|
+
throw new TypeError(`OpenwopClient: major must be a non-negative integer (got ${String(major)})`);
|
|
20
|
+
}
|
|
21
|
+
return `${major}.0`;
|
|
22
|
+
}
|
|
12
23
|
export class OpenwopClient {
|
|
13
24
|
#baseUrl;
|
|
14
25
|
#apiKey;
|
|
15
26
|
#fetch;
|
|
16
27
|
#acceptLanguage;
|
|
28
|
+
#versionHeader;
|
|
29
|
+
/** The `OpenWOP-Version` value this client sends on every request. */
|
|
30
|
+
get protocolVersion() {
|
|
31
|
+
return this.#versionHeader;
|
|
32
|
+
}
|
|
17
33
|
constructor(opts) {
|
|
18
34
|
if (!opts.baseUrl)
|
|
19
35
|
throw new TypeError('OpenwopClient: baseUrl is required');
|
|
@@ -23,121 +39,101 @@ export class OpenwopClient {
|
|
|
23
39
|
this.#apiKey = opts.apiKey;
|
|
24
40
|
this.#fetch = opts.fetch ?? fetch;
|
|
25
41
|
this.#acceptLanguage = opts.acceptLanguage;
|
|
42
|
+
this.#versionHeader = protocolVersionHeader(opts.major ?? SDK_PROTOCOL_MAJOR);
|
|
26
43
|
}
|
|
27
44
|
// ── Discovery ────────────────────────────────────────────────────────
|
|
28
45
|
discovery = {
|
|
46
|
+
/**
|
|
47
|
+
* `GET /.well-known/openwop` — one resource whose representation the
|
|
48
|
+
* `OpenWOP-Version` header selects (capabilities.md §1): with this
|
|
49
|
+
* client's major the host returns the closed v2 root.
|
|
50
|
+
*/
|
|
29
51
|
capabilities: () => this.#request({ method: 'GET', path: '/.well-known/openwop' }, false),
|
|
30
|
-
|
|
52
|
+
/** `GET /openapi.json` — the self-describing OpenAPI 3.1 document. */
|
|
53
|
+
openapi: () => this.#request({ method: 'GET', path: '/openapi.json' }, false),
|
|
31
54
|
};
|
|
32
55
|
// ── Workflows ────────────────────────────────────────────────────────
|
|
33
56
|
workflows = {
|
|
57
|
+
/** `GET /workflows/{workflowId}` */
|
|
34
58
|
get: (workflowId) => this.#request({
|
|
35
59
|
method: 'GET',
|
|
36
|
-
path: `/
|
|
60
|
+
path: `/workflows/${encodeURIComponent(workflowId)}`,
|
|
37
61
|
}),
|
|
38
62
|
};
|
|
39
63
|
// ── Runs ─────────────────────────────────────────────────────────────
|
|
40
64
|
runs = {
|
|
65
|
+
/** `POST /runs` — the body is closed at the composition (runs.md §Create). */
|
|
41
66
|
create: (body, opts = {}) => this.#request({
|
|
42
67
|
method: 'POST',
|
|
43
|
-
path: '/
|
|
68
|
+
path: '/runs',
|
|
44
69
|
body,
|
|
45
70
|
headers: this.#mutationHeaders(opts),
|
|
46
71
|
}),
|
|
72
|
+
/** `GET /runs/{runId}` — the snapshot (runs.md §Snapshot). */
|
|
47
73
|
get: (runId) => this.#request({
|
|
48
74
|
method: 'GET',
|
|
49
|
-
path: `/
|
|
75
|
+
path: `/runs/${encodeURIComponent(runId)}`,
|
|
50
76
|
}),
|
|
51
|
-
/**
|
|
52
|
-
* Fetch the portable JSON diagnostic export for a single run per
|
|
53
|
-
* `spec/v1/debug-bundle.md`. The bundle's `redactionMode` reflects
|
|
54
|
-
* the host's advertised `capabilities.compliance.defaultMode`; the
|
|
55
|
-
* caller MUST treat masked/omitted/hashed fields as the
|
|
56
|
-
* spec-canonical value. The `truncated` + `truncatedReason` fields
|
|
57
|
-
* indicate the host hit its size cap.
|
|
58
|
-
*
|
|
59
|
-
* Returns `null` when the host doesn't advertise
|
|
60
|
-
* `capabilities.debugBundle.supported: true` (the endpoint returns
|
|
61
|
-
* 404 in that case per `debug-bundle.md` §"Authorization").
|
|
62
|
-
*/
|
|
63
|
-
debugBundle: async (runId, opts = {}) => {
|
|
64
|
-
const params = new URLSearchParams();
|
|
65
|
-
if (opts.maxEvents !== undefined)
|
|
66
|
-
params.set('maxEvents', String(opts.maxEvents));
|
|
67
|
-
const query = params.toString();
|
|
68
|
-
const path = `/v1/runs/${encodeURIComponent(runId)}/debug-bundle${query ? `?${query}` : ''}`;
|
|
69
|
-
try {
|
|
70
|
-
return await this.#request({ method: 'GET', path });
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
// Host doesn't advertise the capability → 404. Surface as null so callers
|
|
74
|
-
// can branch on capability discovery without try/catch.
|
|
75
|
-
if (err instanceof WopError && err.status === 404)
|
|
76
|
-
return null;
|
|
77
|
-
throw err;
|
|
78
|
-
}
|
|
79
|
-
},
|
|
77
|
+
/** `POST /runs/{runId}/cancel` — `status` is `cancelling` or `cancelled`. */
|
|
80
78
|
cancel: (runId, body = {}, opts = {}) => this.#request({
|
|
81
79
|
method: 'POST',
|
|
82
|
-
path: `/
|
|
80
|
+
path: `/runs/${encodeURIComponent(runId)}/cancel`,
|
|
83
81
|
body,
|
|
84
82
|
headers: this.#mutationHeaders(opts),
|
|
85
83
|
}),
|
|
84
|
+
/** `POST /runs/{runId}:pause` — `409` when the run is not pausable. */
|
|
86
85
|
pause: (runId, body = {}, opts = {}) => this.#request({
|
|
87
86
|
method: 'POST',
|
|
88
|
-
path: `/
|
|
87
|
+
path: `/runs/${encodeURIComponent(runId)}:pause`,
|
|
89
88
|
body,
|
|
90
89
|
headers: this.#mutationHeaders(opts),
|
|
91
90
|
}),
|
|
91
|
+
/** `POST /runs/{runId}:resume` — `409` when the run is not paused. */
|
|
92
92
|
resume: (runId, body = {}, opts = {}) => this.#request({
|
|
93
93
|
method: 'POST',
|
|
94
|
-
path: `/
|
|
94
|
+
path: `/runs/${encodeURIComponent(runId)}:resume`,
|
|
95
95
|
body,
|
|
96
96
|
headers: this.#mutationHeaders(opts),
|
|
97
97
|
}),
|
|
98
98
|
/**
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
* request reaches the host; partial failures surface inside the
|
|
103
|
-
* array (each entry carries `ok: boolean` + optional `error`). Host-
|
|
104
|
-
* defined cap on `runIds[]` length (RECOMMENDED 100); over-cap
|
|
105
|
-
* returns `400 validation_error` with `details.maxRunIds`.
|
|
99
|
+
* `POST /runs:bulk-cancel` — `200 { results[] }` in request order even
|
|
100
|
+
* when every id failed; per-id authorization yields `ok: false` with
|
|
101
|
+
* `run_forbidden` in that entry, never a top-level `403` (runs.md §Cancel).
|
|
106
102
|
*/
|
|
107
103
|
bulkCancel: (body, opts = {}) => this.#request({
|
|
108
104
|
method: 'POST',
|
|
109
|
-
path: '/
|
|
105
|
+
path: '/runs:bulk-cancel',
|
|
110
106
|
body,
|
|
111
107
|
headers: this.#mutationHeaders(opts),
|
|
112
108
|
}),
|
|
109
|
+
/** `POST /runs/{runId}:fork` — `mode: replay | branch` (runs.md §Fork; replay.md). */
|
|
113
110
|
fork: (runId, body, opts = {}) => this.#request({
|
|
114
111
|
method: 'POST',
|
|
115
|
-
path: `/
|
|
112
|
+
path: `/runs/${encodeURIComponent(runId)}:fork`,
|
|
116
113
|
body,
|
|
117
114
|
headers: this.#mutationHeaders(opts),
|
|
118
115
|
}),
|
|
119
116
|
/**
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
* `capabilities.feedback.supported`).
|
|
117
|
+
* `POST /runs/{runId}/annotations` — a live notification, never a run
|
|
118
|
+
* event. Throws on non-2xx (`501` when the host doesn't advertise
|
|
119
|
+
* `feedback`).
|
|
124
120
|
*/
|
|
125
121
|
createAnnotation: (runId, body, opts = {}) => this.#request({
|
|
126
122
|
method: 'POST',
|
|
127
|
-
path: `/
|
|
123
|
+
path: `/runs/${encodeURIComponent(runId)}/annotations`,
|
|
128
124
|
body,
|
|
129
125
|
headers: this.#mutationHeaders(opts),
|
|
130
126
|
}),
|
|
131
127
|
/**
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
128
|
+
* `GET /runs/{runId}/annotations` — returns `null` when the host doesn't
|
|
129
|
+
* advertise `feedback` (404/501), so callers can branch on capability
|
|
130
|
+
* discovery without try/catch.
|
|
135
131
|
*/
|
|
136
132
|
listAnnotations: async (runId) => {
|
|
137
133
|
try {
|
|
138
134
|
const res = await this.#request({
|
|
139
135
|
method: 'GET',
|
|
140
|
-
path: `/
|
|
136
|
+
path: `/runs/${encodeURIComponent(runId)}/annotations`,
|
|
141
137
|
});
|
|
142
138
|
return res.annotations;
|
|
143
139
|
}
|
|
@@ -148,21 +144,17 @@ export class OpenwopClient {
|
|
|
148
144
|
}
|
|
149
145
|
},
|
|
150
146
|
/**
|
|
151
|
-
*
|
|
152
|
-
* composition chain
|
|
153
|
-
* `parent.wellKnownUrl` is set when the parent is on a different
|
|
154
|
-
* host, so callers walk the chain one hop at a time.
|
|
155
|
-
*
|
|
147
|
+
* `GET /runs/{runId}/ancestry` — the run's immediate parent in the
|
|
148
|
+
* cross-host composition chain; `parent: null` for top-level runs.
|
|
156
149
|
* Returns `null` when the host doesn't advertise
|
|
157
|
-
* `
|
|
158
|
-
* (the endpoint
|
|
159
|
-
* `spec/v1/multi-agent-execution.md` §"GET /v1/runs/{runId}/ancestry").
|
|
150
|
+
* `multiAgent.executionModel.crossHostCausation.ancestryEndpointSupported`
|
|
151
|
+
* (the endpoint 404s).
|
|
160
152
|
*/
|
|
161
153
|
ancestry: async (runId) => {
|
|
162
154
|
try {
|
|
163
155
|
return await this.#request({
|
|
164
156
|
method: 'GET',
|
|
165
|
-
path: `/
|
|
157
|
+
path: `/runs/${encodeURIComponent(runId)}/ancestry`,
|
|
166
158
|
});
|
|
167
159
|
}
|
|
168
160
|
catch (err) {
|
|
@@ -172,18 +164,15 @@ export class OpenwopClient {
|
|
|
172
164
|
}
|
|
173
165
|
},
|
|
174
166
|
/**
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* `
|
|
178
|
-
* implement the endpoint (404 per `spec/v1/rest-endpoints.md`
|
|
179
|
-
* §`GET /v1/runs/{runId}:diff`). `divergedAtSeq` is null + `eventDiffs`
|
|
180
|
-
* empty when the two logs are identical.
|
|
167
|
+
* `GET /runs/{runId}:diff?against=` — deterministic, replay-aware diff of
|
|
168
|
+
* two runs; requires `runs:read` on both. OPTIONAL surface: `null` on
|
|
169
|
+
* `404`. Identical logs yield `divergedAtSeq: null` + empty `eventDiffs`.
|
|
181
170
|
*/
|
|
182
171
|
diff: async (runId, against) => {
|
|
183
172
|
try {
|
|
184
173
|
return await this.#request({
|
|
185
174
|
method: 'GET',
|
|
186
|
-
path: `/
|
|
175
|
+
path: `/runs/${encodeURIComponent(runId)}:diff?against=${encodeURIComponent(against)}`,
|
|
187
176
|
});
|
|
188
177
|
}
|
|
189
178
|
catch (err) {
|
|
@@ -193,18 +182,15 @@ export class OpenwopClient {
|
|
|
193
182
|
}
|
|
194
183
|
},
|
|
195
184
|
/**
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
* redaction-safe safety findings. Returns `null` when the host doesn't
|
|
200
|
-
* advertise `capabilities.agents.evalSuite` or the run isn't an eval run
|
|
201
|
-
* (404). Throws `409` while the run is still in progress.
|
|
185
|
+
* `GET /runs/{runId}/eval-summary` — the `EvalSummary` for a terminal eval
|
|
186
|
+
* run. `null` on `404` (not an eval run, or `agents.evalSuite`
|
|
187
|
+
* unadvertised); throws `409` while the run is still in progress.
|
|
202
188
|
*/
|
|
203
189
|
evalSummary: async (runId) => {
|
|
204
190
|
try {
|
|
205
191
|
return await this.#request({
|
|
206
192
|
method: 'GET',
|
|
207
|
-
path: `/
|
|
193
|
+
path: `/runs/${encodeURIComponent(runId)}/eval-summary`,
|
|
208
194
|
});
|
|
209
195
|
}
|
|
210
196
|
catch (err) {
|
|
@@ -214,15 +200,14 @@ export class OpenwopClient {
|
|
|
214
200
|
}
|
|
215
201
|
},
|
|
216
202
|
/**
|
|
217
|
-
*
|
|
218
|
-
*
|
|
219
|
-
* on 404 (no such artifact, or the host doesn't store artifacts).
|
|
203
|
+
* `GET /runs/{runId}/artifacts/{artifactId}` — an implementation-defined
|
|
204
|
+
* JSON object. `null` on `404`.
|
|
220
205
|
*/
|
|
221
206
|
getArtifact: async (runId, artifactId) => {
|
|
222
207
|
try {
|
|
223
208
|
return await this.#request({
|
|
224
209
|
method: 'GET',
|
|
225
|
-
path: `/
|
|
210
|
+
path: `/runs/${encodeURIComponent(runId)}/artifacts/${encodeURIComponent(artifactId)}`,
|
|
226
211
|
});
|
|
227
212
|
}
|
|
228
213
|
catch (err) {
|
|
@@ -231,10 +216,51 @@ export class OpenwopClient {
|
|
|
231
216
|
throw err;
|
|
232
217
|
}
|
|
233
218
|
},
|
|
219
|
+
/**
|
|
220
|
+
* `GET /runs/{runId}/compensation` (RFC 0173 §C.1) — the compensation
|
|
221
|
+
* plan and attempts. Gated on `compensation`; `null` on `404`.
|
|
222
|
+
*/
|
|
223
|
+
compensation: async (runId) => {
|
|
224
|
+
try {
|
|
225
|
+
return await this.#request({
|
|
226
|
+
method: 'GET',
|
|
227
|
+
path: `/runs/${encodeURIComponent(runId)}/compensation`,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
catch (err) {
|
|
231
|
+
if (err instanceof WopError && err.status === 404)
|
|
232
|
+
return null;
|
|
233
|
+
throw err;
|
|
234
|
+
}
|
|
235
|
+
},
|
|
236
|
+
/**
|
|
237
|
+
* `GET /runs/{runId}/effects` (RFC 0173 §C.2) — the Layer-2 effect ledger,
|
|
238
|
+
* business-identity keyed. Gated on `idempotency`; `null` on `404`.
|
|
239
|
+
*/
|
|
240
|
+
effects: async (runId) => {
|
|
241
|
+
try {
|
|
242
|
+
return await this.#request({
|
|
243
|
+
method: 'GET',
|
|
244
|
+
path: `/runs/${encodeURIComponent(runId)}/effects`,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
if (err instanceof WopError && err.status === 404)
|
|
249
|
+
return null;
|
|
250
|
+
throw err;
|
|
251
|
+
}
|
|
252
|
+
},
|
|
253
|
+
/**
|
|
254
|
+
* `GET /runs/{runId}/events/poll` — the long-poll fallback (events.md
|
|
255
|
+
* §Poll). `afterSequence` returns events with `sequence > afterSequence`;
|
|
256
|
+
* omission means from the first event. The response's `lastSequence` is
|
|
257
|
+
* the highest sequence in the log (`-1` when empty) — feed it back as the
|
|
258
|
+
* next `afterSequence`.
|
|
259
|
+
*/
|
|
234
260
|
pollEvents: (runId, params = {}) => {
|
|
235
261
|
const search = new URLSearchParams();
|
|
236
|
-
if (params.
|
|
237
|
-
search.set('
|
|
262
|
+
if (params.afterSequence !== undefined) {
|
|
263
|
+
search.set('afterSequence', String(params.afterSequence));
|
|
238
264
|
}
|
|
239
265
|
if (params.timeoutSeconds !== undefined) {
|
|
240
266
|
search.set('timeout', String(params.timeoutSeconds));
|
|
@@ -242,25 +268,38 @@ export class OpenwopClient {
|
|
|
242
268
|
const qs = search.toString();
|
|
243
269
|
return this.#request({
|
|
244
270
|
method: 'GET',
|
|
245
|
-
path: `/
|
|
271
|
+
path: `/runs/${encodeURIComponent(runId)}/events/poll${qs ? `?${qs}` : ''}`,
|
|
246
272
|
});
|
|
247
273
|
},
|
|
248
274
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
* loop or call `signal.abort()` to terminate early.
|
|
275
|
+
* `GET /runs/{runId}/events` — async-iterable SSE consumer. The
|
|
276
|
+
* connection auto-closes after the run's terminal event; break out of
|
|
277
|
+
* the loop or call `signal.abort()` to terminate early.
|
|
278
|
+
*/
|
|
279
|
+
events: (runId, opts = {}) => streamEvents(this.#streamContext(), runId, opts),
|
|
280
|
+
};
|
|
281
|
+
// ── Host ─────────────────────────────────────────────────────────────
|
|
282
|
+
host = {
|
|
283
|
+
/**
|
|
284
|
+
* `GET /host/effect-seams` (RFC 0173 §C) — every outbound effect seam
|
|
285
|
+
* replay suppression covers. Throws `401` when unauthenticated.
|
|
252
286
|
*/
|
|
253
|
-
|
|
287
|
+
effectSeams: () => this.#request({ method: 'GET', path: '/host/effect-seams' }),
|
|
288
|
+
/**
|
|
289
|
+
* `GET /host/events` — the `hostEvents` channel (heartbeat messages) as
|
|
290
|
+
* SSE; content-free of run data. A host MAY declare another address
|
|
291
|
+
* under `heartbeat.deliveryChannel` — pass it as `opts.path`.
|
|
292
|
+
*/
|
|
293
|
+
events: (opts = {}) => streamHostEvents(this.#streamContext(), opts),
|
|
254
294
|
};
|
|
255
295
|
// ── Manifest-agent inventory (RFC 0072 §A) ───────────────────────────
|
|
256
|
-
// Read-only. Gated on `
|
|
257
|
-
//
|
|
258
|
-
// Dispatch is not here: a manifest agent runs as a `runs.create` whose
|
|
259
|
-
// workflow node pins it via `WorkflowNode.agent` (RFC 0072 §B).
|
|
296
|
+
// Read-only. Gated on `agents`; the methods return `null` when the host
|
|
297
|
+
// doesn't advertise it (the endpoints 404).
|
|
260
298
|
agents = {
|
|
299
|
+
/** `GET /agents` */
|
|
261
300
|
list: async () => {
|
|
262
301
|
try {
|
|
263
|
-
return await this.#request({ method: 'GET', path: '/
|
|
302
|
+
return await this.#request({ method: 'GET', path: '/agents' });
|
|
264
303
|
}
|
|
265
304
|
catch (err) {
|
|
266
305
|
if (err instanceof WopError && err.status === 404)
|
|
@@ -268,11 +307,12 @@ export class OpenwopClient {
|
|
|
268
307
|
throw err;
|
|
269
308
|
}
|
|
270
309
|
},
|
|
310
|
+
/** `GET /agents/{agentId}` */
|
|
271
311
|
get: async (agentId) => {
|
|
272
312
|
try {
|
|
273
313
|
return await this.#request({
|
|
274
314
|
method: 'GET',
|
|
275
|
-
path: `/
|
|
315
|
+
path: `/agents/${encodeURIComponent(agentId)}`,
|
|
276
316
|
});
|
|
277
317
|
}
|
|
278
318
|
catch (err) {
|
|
@@ -281,17 +321,12 @@ export class OpenwopClient {
|
|
|
281
321
|
throw err;
|
|
282
322
|
}
|
|
283
323
|
},
|
|
284
|
-
/**
|
|
285
|
-
* RFC 0082 §C/§E — list a manifest agent's deployment records (per-(agentId,
|
|
286
|
-
* version) lifecycle state + channels + canary + rollback pointer). Returns
|
|
287
|
-
* `null` when the host doesn't advertise `capabilities.agents.deployment`
|
|
288
|
-
* (the endpoint 404s).
|
|
289
|
-
*/
|
|
324
|
+
/** `GET /agents/{agentId}/deployments` (RFC 0082 §C/§E) — `null` when unadvertised. */
|
|
290
325
|
listDeployments: async (agentId) => {
|
|
291
326
|
try {
|
|
292
327
|
return await this.#request({
|
|
293
328
|
method: 'GET',
|
|
294
|
-
path: `/
|
|
329
|
+
path: `/agents/${encodeURIComponent(agentId)}/deployments`,
|
|
295
330
|
});
|
|
296
331
|
}
|
|
297
332
|
catch (err) {
|
|
@@ -300,28 +335,17 @@ export class OpenwopClient {
|
|
|
300
335
|
throw err;
|
|
301
336
|
}
|
|
302
337
|
},
|
|
303
|
-
/**
|
|
304
|
-
* RFC 0082 §E — request a deployment state transition (promote / pause /
|
|
305
|
-
* deprecate / rollback / adjust-canary). The host authorizes fail-closed
|
|
306
|
-
* against the RFC 0049 `deploy:*` scope, runs any RFC 0051 approvalGate, and
|
|
307
|
-
* enforces RFC 0081 `requiredEval` before emitting `deployment.promoted`.
|
|
308
|
-
* Returns the updated deployment record. Throws on non-2xx (`403` fail-closed
|
|
309
|
-
* / `eval_gate_unmet`; `400` `no_active_deployment` / unsupported state).
|
|
310
|
-
*/
|
|
338
|
+
/** `POST /agents/{agentId}/deployments` (RFC 0082 §E) — a deployment state transition. */
|
|
311
339
|
transitionDeployment: (agentId, body, opts = {}) => this.#request({
|
|
312
340
|
method: 'POST',
|
|
313
|
-
path: `/
|
|
341
|
+
path: `/agents/${encodeURIComponent(agentId)}/deployments`,
|
|
314
342
|
body,
|
|
315
343
|
headers: this.#mutationHeaders(opts),
|
|
316
344
|
}),
|
|
317
|
-
/**
|
|
318
|
-
* RFC 0086 §B — list the standing agent roster (named instances + their
|
|
319
|
-
* workflow portfolios) visible to the caller. Returns `null` when the host
|
|
320
|
-
* doesn't advertise `capabilities.agents.roster` (the endpoint 404s).
|
|
321
|
-
*/
|
|
345
|
+
/** `GET /agents/roster` (RFC 0086 §B) — `null` when unadvertised. */
|
|
322
346
|
listRoster: async () => {
|
|
323
347
|
try {
|
|
324
|
-
return await this.#request({ method: 'GET', path: '/
|
|
348
|
+
return await this.#request({ method: 'GET', path: '/agents/roster' });
|
|
325
349
|
}
|
|
326
350
|
catch (err) {
|
|
327
351
|
if (err instanceof WopError && err.status === 404)
|
|
@@ -329,15 +353,12 @@ export class OpenwopClient {
|
|
|
329
353
|
throw err;
|
|
330
354
|
}
|
|
331
355
|
},
|
|
332
|
-
/**
|
|
333
|
-
* RFC 0086 §B — return one standing roster entry. Returns `null` on 404
|
|
334
|
-
* (no such entry, cross-tenant, or the capability is unadvertised).
|
|
335
|
-
*/
|
|
356
|
+
/** `GET /agents/roster/{rosterId}` (RFC 0086 §B) — `null` on `404`. */
|
|
336
357
|
getRosterEntry: async (rosterId) => {
|
|
337
358
|
try {
|
|
338
359
|
return await this.#request({
|
|
339
360
|
method: 'GET',
|
|
340
|
-
path: `/
|
|
361
|
+
path: `/agents/roster/${encodeURIComponent(rosterId)}`,
|
|
341
362
|
});
|
|
342
363
|
}
|
|
343
364
|
catch (err) {
|
|
@@ -346,14 +367,10 @@ export class OpenwopClient {
|
|
|
346
367
|
throw err;
|
|
347
368
|
}
|
|
348
369
|
},
|
|
349
|
-
/**
|
|
350
|
-
* RFC 0087 §C — return the caller's agent org-chart (departments + roles +
|
|
351
|
-
* `reportsTo` over roster members; descriptive — confers no authority).
|
|
352
|
-
* Returns `null` when the host doesn't advertise `capabilities.agents.orgChart`.
|
|
353
|
-
*/
|
|
370
|
+
/** `GET /agents/org-chart` (RFC 0087 §C) — `null` when unadvertised. */
|
|
354
371
|
getOrgChart: async () => {
|
|
355
372
|
try {
|
|
356
|
-
return await this.#request({ method: 'GET', path: '/
|
|
373
|
+
return await this.#request({ method: 'GET', path: '/agents/org-chart' });
|
|
357
374
|
}
|
|
358
375
|
catch (err) {
|
|
359
376
|
if (err instanceof WopError && err.status === 404)
|
|
@@ -361,18 +378,13 @@ export class OpenwopClient {
|
|
|
361
378
|
throw err;
|
|
362
379
|
}
|
|
363
380
|
},
|
|
364
|
-
/**
|
|
365
|
-
* RFC 0087 §D — one department's subtree + responsibility roll-up (the
|
|
366
|
-
* union of its members' RFC 0086 portfolios). `recursive: false` scopes the
|
|
367
|
-
* roll-up to direct members. Returns `null` on 404 (unknown/cross-tenant
|
|
368
|
-
* department, or the capability is unadvertised).
|
|
369
|
-
*/
|
|
381
|
+
/** `GET /agents/org-chart/{departmentId}` (RFC 0087 §D) — `null` on `404`. */
|
|
370
382
|
getOrgChartDepartment: async (departmentId, opts = {}) => {
|
|
371
383
|
const qs = opts.recursive === false ? '?recursive=false' : '';
|
|
372
384
|
try {
|
|
373
385
|
return await this.#request({
|
|
374
386
|
method: 'GET',
|
|
375
|
-
path: `/
|
|
387
|
+
path: `/agents/org-chart/${encodeURIComponent(departmentId)}${qs}`,
|
|
376
388
|
});
|
|
377
389
|
}
|
|
378
390
|
catch (err) {
|
|
@@ -382,18 +394,12 @@ export class OpenwopClient {
|
|
|
382
394
|
}
|
|
383
395
|
},
|
|
384
396
|
};
|
|
385
|
-
// RFC 0078 — portable tool catalog (
|
|
386
|
-
// projects every node-pack / workflow / mcp / connector / host-extension
|
|
387
|
-
// tool visible to the caller onto a uniform `ToolDescriptor`.
|
|
397
|
+
// ── RFC 0078 — portable tool catalog (gated on `toolCatalog`) ─────────
|
|
388
398
|
tools = {
|
|
389
|
-
/**
|
|
390
|
-
* RFC 0078 §B — list the portable `ToolDescriptor`s visible to the caller.
|
|
391
|
-
* Returns `null` when the host doesn't advertise `capabilities.toolCatalog`
|
|
392
|
-
* (the endpoint 404s), so callers can branch on capability discovery.
|
|
393
|
-
*/
|
|
399
|
+
/** `GET /tools` — `null` when the host doesn't advertise `toolCatalog`. */
|
|
394
400
|
list: async () => {
|
|
395
401
|
try {
|
|
396
|
-
return await this.#request({ method: 'GET', path: '/
|
|
402
|
+
return await this.#request({ method: 'GET', path: '/tools' });
|
|
397
403
|
}
|
|
398
404
|
catch (err) {
|
|
399
405
|
if (err instanceof WopError && err.status === 404)
|
|
@@ -401,17 +407,12 @@ export class OpenwopClient {
|
|
|
401
407
|
throw err;
|
|
402
408
|
}
|
|
403
409
|
},
|
|
404
|
-
/**
|
|
405
|
-
* RFC 0112 — list the `CompactToolDescriptor`s via `GET /v1/tools?view=compact`
|
|
406
|
-
* when the host advertises `capabilities.toolCatalog.compactView`. Unwraps the
|
|
407
|
-
* `{ tools: CompactToolDescriptor[] }` envelope. Returns `null` when the host
|
|
408
|
-
* doesn't advertise the catalog (the endpoint 404s).
|
|
409
|
-
*/
|
|
410
|
+
/** `GET /tools?view=compact` (RFC 0112) — unwraps `{ tools }`; `null` when unadvertised. */
|
|
410
411
|
listCompact: async () => {
|
|
411
412
|
try {
|
|
412
413
|
const res = await this.#request({
|
|
413
414
|
method: 'GET',
|
|
414
|
-
path: '/
|
|
415
|
+
path: '/tools?view=compact',
|
|
415
416
|
});
|
|
416
417
|
return res.tools ?? [];
|
|
417
418
|
}
|
|
@@ -421,18 +422,13 @@ export class OpenwopClient {
|
|
|
421
422
|
throw err;
|
|
422
423
|
}
|
|
423
424
|
},
|
|
424
|
-
/**
|
|
425
|
-
* RFC 0078 §B — return one `ToolDescriptor` by its stable `toolId`. Returns
|
|
426
|
-
* `null` on 404 (no such tool, or the capability is unadvertised). Pass
|
|
427
|
-
* `{ view: 'compact' }` (RFC 0112) to receive the `CompactToolDescriptor`
|
|
428
|
-
* projection instead.
|
|
429
|
-
*/
|
|
425
|
+
/** `GET /tools/{toolId}` — `null` on `404`; `{ view: 'compact' }` for the compact projection. */
|
|
430
426
|
get: async (toolId, opts = {}) => {
|
|
431
427
|
const query = opts.view === 'compact' ? '?view=compact' : '';
|
|
432
428
|
try {
|
|
433
429
|
return await this.#request({
|
|
434
430
|
method: 'GET',
|
|
435
|
-
path: `/
|
|
431
|
+
path: `/tools/${encodeURIComponent(toolId)}${query}`,
|
|
436
432
|
});
|
|
437
433
|
}
|
|
438
434
|
catch (err) {
|
|
@@ -442,136 +438,57 @@ export class OpenwopClient {
|
|
|
442
438
|
}
|
|
443
439
|
},
|
|
444
440
|
};
|
|
445
|
-
// ── User-authored agents (sample-extension; non-normative) ───────────
|
|
446
|
-
// Backs the workflow-engine sample app's Agents tab. Pack-installed
|
|
447
|
-
// agents come through the `.agents` inventory above (RFC 0072 §A).
|
|
448
|
-
// These methods wrap the `POST/DELETE /v1/host/sample/agents` +
|
|
449
|
-
// `GET/POST /v1/host/sample/registry/agent-packs` host extensions.
|
|
450
|
-
// Returns `null` on 404 (capability absent — matches the `.agents`
|
|
451
|
-
// surface pattern); throws on other failures.
|
|
452
|
-
userAgents = {
|
|
453
|
-
create: async (body, opts = {}) => {
|
|
454
|
-
return await this.#request({
|
|
455
|
-
method: 'POST',
|
|
456
|
-
path: '/v1/host/sample/agents',
|
|
457
|
-
body,
|
|
458
|
-
...(opts.idempotencyKey ? { idempotencyKey: opts.idempotencyKey } : {}),
|
|
459
|
-
});
|
|
460
|
-
},
|
|
461
|
-
delete: async (agentId) => {
|
|
462
|
-
try {
|
|
463
|
-
await this.#request({
|
|
464
|
-
method: 'DELETE',
|
|
465
|
-
path: `/v1/host/sample/agents/${encodeURIComponent(agentId)}`,
|
|
466
|
-
});
|
|
467
|
-
return true;
|
|
468
|
-
}
|
|
469
|
-
catch (err) {
|
|
470
|
-
if (err instanceof WopError && err.status === 404)
|
|
471
|
-
return false;
|
|
472
|
-
throw err;
|
|
473
|
-
}
|
|
474
|
-
},
|
|
475
|
-
listAvailablePacks: async () => {
|
|
476
|
-
try {
|
|
477
|
-
return await this.#request({
|
|
478
|
-
method: 'GET',
|
|
479
|
-
path: '/v1/host/sample/registry/agent-packs',
|
|
480
|
-
});
|
|
481
|
-
}
|
|
482
|
-
catch (err) {
|
|
483
|
-
if (err instanceof WopError && err.status === 404)
|
|
484
|
-
return null;
|
|
485
|
-
throw err;
|
|
486
|
-
}
|
|
487
|
-
},
|
|
488
|
-
installPack: async (body) => {
|
|
489
|
-
return await this.#request({
|
|
490
|
-
method: 'POST',
|
|
491
|
-
path: '/v1/host/sample/registry/agent-packs/install',
|
|
492
|
-
body,
|
|
493
|
-
});
|
|
494
|
-
},
|
|
495
|
-
};
|
|
496
441
|
// ── HITL interrupts (run-scoped + signed-token) ──────────────────────
|
|
497
442
|
interrupts = {
|
|
443
|
+
/** `POST /runs/{runId}/interrupts/{nodeId}` */
|
|
498
444
|
resolveByRun: (runId, nodeId, body, opts = {}) => this.#request({
|
|
499
445
|
method: 'POST',
|
|
500
|
-
path: `/
|
|
446
|
+
path: `/runs/${encodeURIComponent(runId)}/interrupts/${encodeURIComponent(nodeId)}`,
|
|
501
447
|
body,
|
|
502
448
|
headers: this.#mutationHeaders(opts),
|
|
503
449
|
}),
|
|
504
450
|
/**
|
|
505
|
-
*
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* required (signed-token endpoints intentionally bypass bearer
|
|
509
|
-
* auth so external systems can resolve without openwop credentials).
|
|
451
|
+
* `GET /interrupts/{token}` — inspect via signed token. The token is the
|
|
452
|
+
* auth; no API key is sent (signed-token endpoints bypass bearer auth so
|
|
453
|
+
* external systems can resolve without openwop credentials).
|
|
510
454
|
*/
|
|
511
455
|
inspectByToken: (token) => this.#request({
|
|
512
456
|
method: 'GET',
|
|
513
|
-
path: `/
|
|
457
|
+
path: `/interrupts/${encodeURIComponent(token)}`,
|
|
514
458
|
}, false),
|
|
515
|
-
/**
|
|
516
|
-
* Resolve an interrupt via signed token — used by external
|
|
517
|
-
* systems (calendar webhooks, payment confirmations) that the
|
|
518
|
-
* engine handed a callback URL at suspension time.
|
|
519
|
-
*/
|
|
459
|
+
/** `POST /interrupts/{token}` — resolve via signed token. */
|
|
520
460
|
resolveByToken: (token, body, opts = {}) => this.#request({
|
|
521
461
|
method: 'POST',
|
|
522
|
-
path: `/
|
|
462
|
+
path: `/interrupts/${encodeURIComponent(token)}`,
|
|
523
463
|
body,
|
|
524
464
|
headers: this.#mutationHeaders(opts),
|
|
525
465
|
}, false),
|
|
526
466
|
};
|
|
527
|
-
// ── Webhook subscriptions (
|
|
467
|
+
// ── Webhook subscriptions (webhooks.md; gated on `webhooks`) ─────────
|
|
528
468
|
webhooks = {
|
|
529
469
|
/**
|
|
530
|
-
*
|
|
531
|
-
* HMAC-SHA256 over
|
|
532
|
-
*
|
|
533
|
-
*
|
|
534
|
-
* server-side for verification; the host cannot recover it.
|
|
470
|
+
* `POST /webhooks` — `{ url, events[], secret?, tags? }`; `url` MUST be
|
|
471
|
+
* `https://`. Deliveries are signed HMAC-SHA256 over
|
|
472
|
+
* `${timestamp}.${rawBody}` under the `OpenWOP-*` header family (verify
|
|
473
|
+
* with `@openwop/openwop/webhooks`).
|
|
535
474
|
*/
|
|
536
475
|
register: (body, opts = {}) => this.#request({
|
|
537
476
|
method: 'POST',
|
|
538
|
-
path: '/
|
|
477
|
+
path: '/webhooks',
|
|
539
478
|
body,
|
|
540
479
|
headers: this.#mutationHeaders(opts),
|
|
541
480
|
}),
|
|
542
|
-
/**
|
|
543
|
-
|
|
544
|
-
* throws `WopError` with `subscription_not_found` on unknown
|
|
545
|
-
* subscriptionId.
|
|
546
|
-
*/
|
|
547
|
-
unregister: async (subscriptionId) => {
|
|
481
|
+
/** `DELETE /webhooks/{webhookId}` — `204`; throws `404` when unknown, `403` outside the tenant. */
|
|
482
|
+
unregister: async (webhookId) => {
|
|
548
483
|
await this.#request({
|
|
549
484
|
method: 'DELETE',
|
|
550
|
-
path: `/
|
|
485
|
+
path: `/webhooks/${encodeURIComponent(webhookId)}`,
|
|
551
486
|
});
|
|
552
487
|
},
|
|
553
488
|
};
|
|
554
|
-
// ── Prompt library (RFC 0028; gated on
|
|
555
|
-
//
|
|
556
|
-
// Read endpoints (list, get, render) gate on
|
|
557
|
-
// `capabilities.prompts.endpointsSupported: true`. Mutating endpoints
|
|
558
|
-
// (create, update, delete) additionally require
|
|
559
|
-
// `capabilities.prompts.mutableLibrary: true`. Hosts that don't advertise
|
|
560
|
-
// the relevant capability return `501 capability_not_provided`; the SDK
|
|
561
|
-
// surfaces that as a `WopError`. Clients SHOULD pre-flight via
|
|
562
|
-
// `getCapabilities()` before calling.
|
|
563
|
-
//
|
|
564
|
-
// NOTE: `capabilities.prompts.supported: true` (without
|
|
565
|
-
// `endpointsSupported: true`) ONLY gates node-execution PromptRef
|
|
566
|
-
// resolution per RFC 0027 Phase A; it does NOT imply these endpoints are
|
|
567
|
-
// available. See spec/v1/prompts.md §"Capability advertisement" for the
|
|
568
|
-
// two-axis gating split.
|
|
489
|
+
// ── Prompt library (RFC 0028; gated on `prompts`) ────────────────────
|
|
569
490
|
prompts = {
|
|
570
|
-
/**
|
|
571
|
-
* List prompt templates available to the caller per RFC 0028 §A
|
|
572
|
-
* (operationId `listPromptTemplates`). Supports kind / tag / modelClass
|
|
573
|
-
* / source filters + opaque cursor pagination.
|
|
574
|
-
*/
|
|
491
|
+
/** `GET /prompts` — kind / tag / modelClass / source filters + cursor pagination. */
|
|
575
492
|
list: (req = {}) => {
|
|
576
493
|
const search = new URLSearchParams();
|
|
577
494
|
if (req.kind)
|
|
@@ -589,15 +506,10 @@ export class OpenwopClient {
|
|
|
589
506
|
const query = search.toString();
|
|
590
507
|
return this.#request({
|
|
591
508
|
method: 'GET',
|
|
592
|
-
path: `/
|
|
509
|
+
path: `/prompts${query ? `?${query}` : ''}`,
|
|
593
510
|
});
|
|
594
511
|
},
|
|
595
|
-
/**
|
|
596
|
-
* Fetch a single PromptTemplate by id per RFC 0028 §A
|
|
597
|
-
* (operationId `getPromptTemplate`). Optionally pin a SemVer
|
|
598
|
-
* `version`; supply `libraryId` to disambiguate when multiple installed
|
|
599
|
-
* packs ship the same templateId.
|
|
600
|
-
*/
|
|
512
|
+
/** `GET /prompts/{templateId}` — `null` on `404`; a `400 prompt_ref_ambiguous` still throws. */
|
|
601
513
|
get: async (req) => {
|
|
602
514
|
const search = new URLSearchParams();
|
|
603
515
|
if (req.version)
|
|
@@ -608,90 +520,55 @@ export class OpenwopClient {
|
|
|
608
520
|
try {
|
|
609
521
|
return await this.#request({
|
|
610
522
|
method: 'GET',
|
|
611
|
-
path: `/
|
|
523
|
+
path: `/prompts/${encodeURIComponent(req.templateId)}${query ? `?${query}` : ''}`,
|
|
612
524
|
});
|
|
613
525
|
}
|
|
614
526
|
catch (err) {
|
|
615
|
-
// Return null on 404 (no such template), consistent with the other
|
|
616
|
-
// get-by-id methods (`agents.get`, `tools.get`, …) and the Python/Go
|
|
617
|
-
// SDKs; a `400 prompt_ref_ambiguous` and other errors still throw so
|
|
618
|
-
// callers can distinguish "not found" from "ambiguous reference".
|
|
619
527
|
if (err instanceof WopError && err.status === 404)
|
|
620
528
|
return null;
|
|
621
529
|
throw err;
|
|
622
530
|
}
|
|
623
531
|
},
|
|
624
|
-
/**
|
|
625
|
-
* Render a PromptTemplate with supplied variable bindings per RFC 0028
|
|
626
|
-
* §A (operationId `renderPromptTemplate`). Returns composed body +
|
|
627
|
-
* sha256 hash + per-variable hashes. The deterministic-hash invariant
|
|
628
|
-
* (RFC 0028 §A) requires the `hash` to match what a matching
|
|
629
|
-
* `prompt.composed` event would carry at dispatch time. Does NOT
|
|
630
|
-
* dispatch an LLM call. Secret-source variable values MUST be supplied
|
|
631
|
-
* as `[REDACTED:<credentialRef>]` markers per
|
|
632
|
-
* SECURITY/threat-model-secret-leakage.md §SR-1.
|
|
633
|
-
*/
|
|
532
|
+
/** `POST /prompts:render` — composed body + sha256 hash; does NOT dispatch an LLM call. */
|
|
634
533
|
render: (req) => {
|
|
635
534
|
return this.#request({
|
|
636
535
|
method: 'POST',
|
|
637
|
-
path: '/
|
|
536
|
+
path: '/prompts:render',
|
|
638
537
|
body: req,
|
|
639
538
|
});
|
|
640
539
|
},
|
|
641
|
-
/**
|
|
642
|
-
* Create a new user-source PromptTemplate per RFC 0028 §A
|
|
643
|
-
* (operationId `createPromptTemplate`). Mutating endpoint —
|
|
644
|
-
* requires `capabilities.prompts.mutableLibrary: true`. Supports
|
|
645
|
-
* `Idempotency-Key` per the standard `MutationOptions` pattern.
|
|
646
|
-
*/
|
|
540
|
+
/** `POST /prompts` — requires `prompts.mutableLibrary`. */
|
|
647
541
|
create: (template, opts = {}) => {
|
|
648
542
|
return this.#request({
|
|
649
543
|
method: 'POST',
|
|
650
|
-
path: '/
|
|
544
|
+
path: '/prompts',
|
|
651
545
|
body: template,
|
|
652
546
|
headers: this.#mutationHeaders(opts),
|
|
653
547
|
});
|
|
654
548
|
},
|
|
655
|
-
/**
|
|
656
|
-
* Replace an existing user-source PromptTemplate per RFC 0028 §A
|
|
657
|
-
* (operationId `updatePromptTemplate`). Submitted SemVer MUST be
|
|
658
|
-
* strictly greater than stored. Mutating endpoint — requires
|
|
659
|
-
* `capabilities.prompts.mutableLibrary: true`. Pack-sourced and
|
|
660
|
-
* host-built-in templates are read-only (host returns 403).
|
|
661
|
-
*/
|
|
549
|
+
/** `PUT /prompts/{templateId}` — the submitted SemVer MUST be strictly greater than stored. */
|
|
662
550
|
update: (templateId, template, opts = {}) => {
|
|
663
551
|
return this.#request({
|
|
664
552
|
method: 'PUT',
|
|
665
|
-
path: `/
|
|
553
|
+
path: `/prompts/${encodeURIComponent(templateId)}`,
|
|
666
554
|
body: template,
|
|
667
555
|
headers: this.#mutationHeaders(opts),
|
|
668
556
|
});
|
|
669
557
|
},
|
|
670
|
-
/**
|
|
671
|
-
* Delete a user-source PromptTemplate per RFC 0028 §A
|
|
672
|
-
* (operationId `deletePromptTemplate`). Mutating endpoint —
|
|
673
|
-
* requires `capabilities.prompts.mutableLibrary: true`. Pack-sourced
|
|
674
|
-
* and host-built-in templates are read-only (host returns 403).
|
|
675
|
-
*/
|
|
558
|
+
/** `DELETE /prompts/{templateId}` */
|
|
676
559
|
delete: (templateId) => {
|
|
677
560
|
return this.#request({
|
|
678
561
|
method: 'DELETE',
|
|
679
|
-
path: `/
|
|
562
|
+
path: `/prompts/${encodeURIComponent(templateId)}`,
|
|
680
563
|
});
|
|
681
564
|
},
|
|
682
565
|
};
|
|
683
|
-
// ── Audit-log integrity
|
|
566
|
+
// ── Audit-log integrity ──────────────────────────────────────────────
|
|
684
567
|
audit = {
|
|
685
568
|
/**
|
|
686
|
-
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
* verdict + signed checkpoints + any detected anomalies.
|
|
690
|
-
*
|
|
691
|
-
* Hosts that do NOT advertise the profile return `404` and throw
|
|
692
|
-
* a `WopError`. Clients SHOULD pre-flight via
|
|
693
|
-
* `client.getCapabilities()` (or directly inspect
|
|
694
|
-
* `capabilities.auth.profiles[]`) before calling.
|
|
569
|
+
* `GET /audit/verify?fromSeq&toSeq` — chain-validity verdict + signed
|
|
570
|
+
* checkpoints + anomalies. Requires the `audit:read` scope; a host that
|
|
571
|
+
* does not serve the profile answers `404`.
|
|
695
572
|
*/
|
|
696
573
|
verify: (fromSeq, toSeq) => {
|
|
697
574
|
const search = new URLSearchParams();
|
|
@@ -699,19 +576,18 @@ export class OpenwopClient {
|
|
|
699
576
|
search.set('toSeq', String(toSeq));
|
|
700
577
|
return this.#request({
|
|
701
578
|
method: 'GET',
|
|
702
|
-
path: `/
|
|
579
|
+
path: `/audit/verify?${search.toString()}`,
|
|
703
580
|
});
|
|
704
581
|
},
|
|
705
582
|
};
|
|
706
|
-
// ── RFC 0103 Localized content surface (gated on
|
|
583
|
+
// ── RFC 0103 Localized content surface (gated on `content`) ──────────
|
|
707
584
|
content = {
|
|
708
|
-
/** `GET /
|
|
709
|
-
* host doesn't advertise `capabilities.content` (501). */
|
|
585
|
+
/** `GET /content/pages` — `null` when the host doesn't advertise `content` (501). */
|
|
710
586
|
listPages: async () => {
|
|
711
587
|
try {
|
|
712
588
|
return await this.#request({
|
|
713
589
|
method: 'GET',
|
|
714
|
-
path: '/
|
|
590
|
+
path: '/content/pages',
|
|
715
591
|
});
|
|
716
592
|
}
|
|
717
593
|
catch (err) {
|
|
@@ -720,15 +596,12 @@ export class OpenwopClient {
|
|
|
720
596
|
throw err;
|
|
721
597
|
}
|
|
722
598
|
},
|
|
723
|
-
/** `GET /
|
|
724
|
-
* sections. `acceptLanguage` rides the `Accept-Language` header (the Stable
|
|
725
|
-
* `i18n.md` negotiation; no `?locale=`). Returns `null` on `404`
|
|
726
|
-
* (no such published page) or `501` (uncapable). */
|
|
599
|
+
/** `GET /content/pages/{slug}` — `acceptLanguage` rides `Accept-Language`; `null` on `404`/`501`. */
|
|
727
600
|
getPage: async (slug, acceptLanguage) => {
|
|
728
601
|
try {
|
|
729
602
|
return await this.#request({
|
|
730
603
|
method: 'GET',
|
|
731
|
-
path: `/
|
|
604
|
+
path: `/content/pages/${encodeURIComponent(slug)}`,
|
|
732
605
|
...(acceptLanguage
|
|
733
606
|
? { headers: { 'Accept-Language': acceptLanguage } }
|
|
734
607
|
: {}),
|
|
@@ -740,27 +613,24 @@ export class OpenwopClient {
|
|
|
740
613
|
throw err;
|
|
741
614
|
}
|
|
742
615
|
},
|
|
743
|
-
/** `POST /
|
|
744
|
-
* typed `WopError` on `400`/`401`/`403`. */
|
|
616
|
+
/** `POST /content/pages` (admin). */
|
|
745
617
|
createPage: (body) => this.#request({
|
|
746
618
|
method: 'POST',
|
|
747
|
-
path: '/
|
|
619
|
+
path: '/content/pages',
|
|
748
620
|
body,
|
|
749
621
|
}),
|
|
750
|
-
/** `PUT /
|
|
751
|
-
* section's field overlay for a locale (admin). */
|
|
622
|
+
/** `PUT /content/pages/{pageId}/sections/{sectionId}` (admin). */
|
|
752
623
|
putSection: (pageId, sectionId, body) => this.#request({
|
|
753
624
|
method: 'PUT',
|
|
754
|
-
path: `/
|
|
625
|
+
path: `/content/pages/${encodeURIComponent(pageId)}/sections/${encodeURIComponent(sectionId)}`,
|
|
755
626
|
body,
|
|
756
627
|
}),
|
|
757
|
-
/** `GET /
|
|
758
|
-
* host doesn't advertise `capabilities.content` (501). */
|
|
628
|
+
/** `GET /content/settings` — `null` when the host doesn't advertise `content` (501). */
|
|
759
629
|
getSettings: async () => {
|
|
760
630
|
try {
|
|
761
631
|
return await this.#request({
|
|
762
632
|
method: 'GET',
|
|
763
|
-
path: '/
|
|
633
|
+
path: '/content/settings',
|
|
764
634
|
});
|
|
765
635
|
}
|
|
766
636
|
catch (err) {
|
|
@@ -769,127 +639,45 @@ export class OpenwopClient {
|
|
|
769
639
|
throw err;
|
|
770
640
|
}
|
|
771
641
|
},
|
|
772
|
-
/** `PUT /
|
|
642
|
+
/** `PUT /content/settings` (admin). */
|
|
773
643
|
putSettings: (body) => this.#request({
|
|
774
644
|
method: 'PUT',
|
|
775
|
-
path: '/
|
|
645
|
+
path: '/content/settings',
|
|
776
646
|
body,
|
|
777
647
|
}),
|
|
778
648
|
};
|
|
779
|
-
// ── RFC 0099 Trigger subscriptions (gated on
|
|
649
|
+
// ── RFC 0099 Trigger subscriptions (gated on `triggerBridge`) ─────────
|
|
780
650
|
triggerSubscriptions = {
|
|
781
|
-
/** `POST /
|
|
782
|
-
* The `binding.secret*` is returned ONCE at creation (SR-1); persist it.
|
|
783
|
-
* Throws the typed `WopError` on `400`/`401`/`403`, or `501` when the host
|
|
784
|
-
* doesn't advertise the trigger-bridge ingestion surface. */
|
|
651
|
+
/** `POST /trigger-subscriptions` — the `binding.secret*` is returned ONCE; persist it. */
|
|
785
652
|
create: (body) => this.#request({
|
|
786
653
|
method: 'POST',
|
|
787
|
-
path: '/
|
|
654
|
+
path: '/trigger-subscriptions',
|
|
788
655
|
body,
|
|
789
656
|
}),
|
|
790
657
|
};
|
|
791
|
-
// ── Agent workspace files (RFC 0059; gated on capabilities.workspace) ──
|
|
792
|
-
workspace = {
|
|
793
|
-
/**
|
|
794
|
-
* RFC 0059 — list workspace file metadata (no bodies) for the caller's
|
|
795
|
-
* `{tenant, workspace}`. Optional `prefix` filters the flat `path`
|
|
796
|
-
* namespace. Returns `null` when the host doesn't advertise
|
|
797
|
-
* `capabilities.workspace.supported` (501), so callers can branch on
|
|
798
|
-
* capability discovery without try/catch.
|
|
799
|
-
*/
|
|
800
|
-
listFiles: async (opts = {}) => {
|
|
801
|
-
const search = new URLSearchParams();
|
|
802
|
-
if (opts.prefix !== undefined)
|
|
803
|
-
search.set('prefix', opts.prefix);
|
|
804
|
-
const qs = search.toString();
|
|
805
|
-
try {
|
|
806
|
-
const res = await this.#request({
|
|
807
|
-
method: 'GET',
|
|
808
|
-
path: `/v1/host/workspace/files${qs ? `?${qs}` : ''}`,
|
|
809
|
-
});
|
|
810
|
-
return res.files;
|
|
811
|
-
}
|
|
812
|
-
catch (err) {
|
|
813
|
-
if (err instanceof WopError && err.status === 501)
|
|
814
|
-
return null;
|
|
815
|
-
throw err;
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
/**
|
|
819
|
-
* RFC 0059 — read one workspace file. Pass `version` for a historical
|
|
820
|
-
* snapshot when `capabilities.workspace.versioned`. Returns `null` when
|
|
821
|
-
* the file is absent (404) or the host doesn't advertise the capability
|
|
822
|
-
* (501).
|
|
823
|
-
*/
|
|
824
|
-
getFile: async (path, opts = {}) => {
|
|
825
|
-
const search = new URLSearchParams();
|
|
826
|
-
if (opts.version !== undefined)
|
|
827
|
-
search.set('version', String(opts.version));
|
|
828
|
-
const qs = search.toString();
|
|
829
|
-
try {
|
|
830
|
-
return await this.#request({
|
|
831
|
-
method: 'GET',
|
|
832
|
-
path: `/v1/host/workspace/files/${encodeURIComponent(path)}${qs ? `?${qs}` : ''}`,
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
catch (err) {
|
|
836
|
-
if (err instanceof WopError && (err.status === 404 || err.status === 501))
|
|
837
|
-
return null;
|
|
838
|
-
throw err;
|
|
839
|
-
}
|
|
840
|
-
},
|
|
841
|
-
/**
|
|
842
|
-
* RFC 0059 — atomic create/replace of a workspace file. Pass `ifMatch`
|
|
843
|
-
* (the file's current `etag`) for optimistic concurrency; a stale token
|
|
844
|
-
* throws a `WopError` with status `409` (`workspace_conflict`). Content
|
|
845
|
-
* beyond `capabilities.workspace.maxFileBytes` throws `413`
|
|
846
|
-
* (`workspace_too_large`). Returns the persisted `WorkspaceFile`.
|
|
847
|
-
*/
|
|
848
|
-
putFile: (path, body, opts = {}) => {
|
|
849
|
-
const headers = this.#mutationHeaders(opts);
|
|
850
|
-
if (opts.ifMatch !== undefined)
|
|
851
|
-
headers['If-Match'] = opts.ifMatch;
|
|
852
|
-
return this.#request({
|
|
853
|
-
method: 'PUT',
|
|
854
|
-
path: `/v1/host/workspace/files/${encodeURIComponent(path)}`,
|
|
855
|
-
body,
|
|
856
|
-
headers,
|
|
857
|
-
});
|
|
858
|
-
},
|
|
859
|
-
/**
|
|
860
|
-
* RFC 0059 — delete a workspace file. Returns `true` on success (`204`),
|
|
861
|
-
* `false` when the file is absent (404) or the host doesn't advertise the
|
|
862
|
-
* capability (501).
|
|
863
|
-
*/
|
|
864
|
-
deleteFile: async (path, opts = {}) => {
|
|
865
|
-
try {
|
|
866
|
-
await this.#request({
|
|
867
|
-
method: 'DELETE',
|
|
868
|
-
path: `/v1/host/workspace/files/${encodeURIComponent(path)}`,
|
|
869
|
-
headers: this.#mutationHeaders(opts),
|
|
870
|
-
});
|
|
871
|
-
return true;
|
|
872
|
-
}
|
|
873
|
-
catch (err) {
|
|
874
|
-
if (err instanceof WopError && (err.status === 404 || err.status === 501))
|
|
875
|
-
return false;
|
|
876
|
-
throw err;
|
|
877
|
-
}
|
|
878
|
-
},
|
|
879
|
-
};
|
|
880
658
|
// ── Internals ────────────────────────────────────────────────────────
|
|
659
|
+
#streamContext() {
|
|
660
|
+
return {
|
|
661
|
+
baseUrl: this.#baseUrl,
|
|
662
|
+
apiKey: this.#apiKey,
|
|
663
|
+
protocolVersion: this.#versionHeader,
|
|
664
|
+
fetch: this.#fetch,
|
|
665
|
+
};
|
|
666
|
+
}
|
|
881
667
|
#mutationHeaders(opts) {
|
|
882
668
|
const h = {};
|
|
883
669
|
if (opts.idempotencyKey)
|
|
884
670
|
h['Idempotency-Key'] = opts.idempotencyKey;
|
|
885
671
|
if (opts.dedup)
|
|
886
|
-
h['
|
|
672
|
+
h['OpenWOP-Dedup'] = opts.dedup;
|
|
887
673
|
return h;
|
|
888
674
|
}
|
|
889
675
|
async #request(opts, authenticated = true) {
|
|
890
676
|
const url = `${this.#baseUrl}${opts.path}`;
|
|
891
677
|
const headers = {
|
|
892
678
|
Accept: 'application/json',
|
|
679
|
+
// RFC 0172 §A.3 — on every request, authenticated or not.
|
|
680
|
+
'OpenWOP-Version': this.#versionHeader,
|
|
893
681
|
...(opts.headers ?? {}),
|
|
894
682
|
};
|
|
895
683
|
if (opts.body !== undefined && headers['Content-Type'] === undefined) {
|
|
@@ -898,7 +686,7 @@ export class OpenwopClient {
|
|
|
898
686
|
if (authenticated) {
|
|
899
687
|
headers.Authorization = `Bearer ${this.#apiKey}`;
|
|
900
688
|
}
|
|
901
|
-
if (this.#acceptLanguage) {
|
|
689
|
+
if (this.#acceptLanguage && headers['Accept-Language'] === undefined) {
|
|
902
690
|
headers['Accept-Language'] = this.#acceptLanguage;
|
|
903
691
|
}
|
|
904
692
|
const init = { method: opts.method, headers };
|
|
@@ -910,17 +698,15 @@ export class OpenwopClient {
|
|
|
910
698
|
}
|
|
911
699
|
const res = await this.#fetch(url, init);
|
|
912
700
|
const text = await res.text();
|
|
913
|
-
// Capture traceparent for error reporting
|
|
914
|
-
//
|
|
915
|
-
// RFC 9110; fetch normalizes to lowercase but be defensive.
|
|
701
|
+
// Capture traceparent for error reporting (observability.md §Trace
|
|
702
|
+
// context propagation). Header names are case-insensitive per RFC 9110.
|
|
916
703
|
const traceparent = res.headers.get('traceparent') ?? res.headers.get('Traceparent') ?? undefined;
|
|
917
704
|
if (!res.ok) {
|
|
918
705
|
let env;
|
|
919
706
|
try {
|
|
920
707
|
const parsed = text.length > 0 ? JSON.parse(text) : undefined;
|
|
921
|
-
if (parsed
|
|
708
|
+
if (isErrorEnvelope(parsed))
|
|
922
709
|
env = parsed;
|
|
923
|
-
}
|
|
924
710
|
}
|
|
925
711
|
catch {
|
|
926
712
|
// not JSON; leave envelope undefined
|
|
@@ -934,10 +720,17 @@ export class OpenwopClient {
|
|
|
934
720
|
}
|
|
935
721
|
catch {
|
|
936
722
|
throw new WopError(res.status, text, {
|
|
937
|
-
error: '
|
|
723
|
+
error: 'internal_error',
|
|
938
724
|
message: 'Server returned non-JSON body for a 2xx response',
|
|
725
|
+
details: { sdk: 'invalid_json' },
|
|
939
726
|
}, traceparent);
|
|
940
727
|
}
|
|
941
728
|
}
|
|
942
729
|
}
|
|
730
|
+
function isErrorEnvelope(value) {
|
|
731
|
+
if (typeof value !== 'object' || value === null)
|
|
732
|
+
return false;
|
|
733
|
+
const rec = value;
|
|
734
|
+
return typeof rec['error'] === 'string' && typeof rec['message'] === 'string';
|
|
735
|
+
}
|
|
943
736
|
//# sourceMappingURL=client.js.map
|