@openwop/openwop 1.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/LICENSE +201 -0
- package/README.md +149 -0
- package/dist/client.d.ts +72 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +178 -0
- package/dist/client.js.map +1 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/run-helpers.d.ts +126 -0
- package/dist/run-helpers.d.ts.map +1 -0
- package/dist/run-helpers.js +182 -0
- package/dist/run-helpers.js.map +1 -0
- package/dist/sse.d.ts +35 -0
- package/dist/sse.d.ts.map +1 -0
- package/dist/sse.js +137 -0
- package/dist/sse.js.map +1 -0
- package/dist/types.d.ts +171 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +57 -0
- package/dist/types.js.map +1 -0
- package/package.json +41 -0
- package/src/client.ts +277 -0
- package/src/index.ts +57 -0
- package/src/run-helpers.ts +235 -0
- package/src/sse.ts +167 -0
- package/src/types.ts +224 -0
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run-status + error-code helpers — additive utilities over the wire-format
|
|
3
|
+
* `RunStatus` union, HTTP error envelopes, and run-level error shape
|
|
4
|
+
* declared in `types.ts`.
|
|
5
|
+
*
|
|
6
|
+
* **Why these live here.** `types.ts` declares the canonical wire shapes
|
|
7
|
+
* (mirroring `api/openapi.yaml` + `schemas/run-snapshot.schema.json`).
|
|
8
|
+
* This module adds the constants + predicates SDK consumers need to act
|
|
9
|
+
* on those shapes without redefining them locally. Pulling them into the
|
|
10
|
+
* SDK makes the protocol vocabulary single-sourced for application,
|
|
11
|
+
* integration, and test code.
|
|
12
|
+
*
|
|
13
|
+
* **Forward-compatibility design.** The spec's `RunStatus` enum is
|
|
14
|
+
* intentionally narrower than what host engines may emit: the schema
|
|
15
|
+
* declares *"future statuses MAY be added; readers SHOULD treat unknown
|
|
16
|
+
* values as terminal-unknown rather than throw"*. So `isTerminalRunStatus`
|
|
17
|
+
* accepts `string` and uses a **negative-set** check — anything NOT in
|
|
18
|
+
* the small known-non-terminal set is treated as terminal. This keeps
|
|
19
|
+
* the helper correct against:
|
|
20
|
+
*
|
|
21
|
+
* - the canonical 8-member spec union (`pending` / `running` / `paused`
|
|
22
|
+
* / `waiting-approval` / `waiting-input` / `completed` / `failed` /
|
|
23
|
+
* `cancelled`)
|
|
24
|
+
* - host extensions like `'planned'`, `'executing'`, `'waiting-external'`,
|
|
25
|
+
* `'timed-out'`, `'interrupted'` which the OpenWOP engine emits
|
|
26
|
+
* - any future spec additions before the SDK ships an updated minor
|
|
27
|
+
*
|
|
28
|
+
* @module @openwop/openwop/run-helpers
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import type { RunStatus } from './types.js';
|
|
32
|
+
|
|
33
|
+
// ─── Run statuses ────────────────────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Run statuses considered active — the run MAY still transition. A reader
|
|
37
|
+
* who does not recognize a status string MUST treat it as terminal-unknown
|
|
38
|
+
* (per the spec's forward-compat clause). This list is the narrow,
|
|
39
|
+
* spec-stable enumeration used to derive that decision.
|
|
40
|
+
*/
|
|
41
|
+
export const ACTIVE_RUN_STATUSES = [
|
|
42
|
+
'pending',
|
|
43
|
+
'running',
|
|
44
|
+
'paused',
|
|
45
|
+
'waiting-approval',
|
|
46
|
+
'waiting-input',
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Spec-known terminal statuses. Hosts MAY emit additional terminal values
|
|
51
|
+
* (e.g., `'timed-out'`, `'interrupted'`); use {@link isTerminalRunStatus}
|
|
52
|
+
* for forward-compatible checks instead of literal-set membership.
|
|
53
|
+
*/
|
|
54
|
+
export const TERMINAL_RUN_STATUSES = [
|
|
55
|
+
'completed',
|
|
56
|
+
'failed',
|
|
57
|
+
'cancelled',
|
|
58
|
+
] as const;
|
|
59
|
+
|
|
60
|
+
export type ActiveRunStatus = (typeof ACTIVE_RUN_STATUSES)[number];
|
|
61
|
+
export type TerminalRunStatus = (typeof TERMINAL_RUN_STATUSES)[number];
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Returns true if the status indicates the run will not transition further.
|
|
65
|
+
*
|
|
66
|
+
* Implemented as a negative check against {@link ACTIVE_RUN_STATUSES}: any
|
|
67
|
+
* value NOT in the spec's known-active set is treated as terminal. This
|
|
68
|
+
* implements the schema's forward-compat clause — a host that emits a
|
|
69
|
+
* status the SDK doesn't know about is assumed to be reporting a terminal
|
|
70
|
+
* state, NOT that the run is still active. The alternative (positive check
|
|
71
|
+
* against {@link TERMINAL_RUN_STATUSES}) would loop polling forever on any
|
|
72
|
+
* unknown value, which is the documented worst-case the clause prevents.
|
|
73
|
+
*
|
|
74
|
+
* Accepts `string` (not just `RunStatus`) so callers can pass values from
|
|
75
|
+
* extended host emissions without casting.
|
|
76
|
+
*/
|
|
77
|
+
export function isTerminalRunStatus(status: RunStatus | string): boolean {
|
|
78
|
+
return !(ACTIVE_RUN_STATUSES as readonly string[]).includes(status);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ─── HTTP error-envelope codes ───────────────────────────────────────────
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Canonical REST/MCP error-envelope codes from `auth.md`,
|
|
85
|
+
* `rest-endpoints.md`, and adjacent v1 specs. The HTTP envelope's `error`
|
|
86
|
+
* field remains string-typed for forward compatibility, but this list
|
|
87
|
+
* gives SDK consumers a stable set for common branching.
|
|
88
|
+
*
|
|
89
|
+
* Codes here describe request/transport failures. Run execution failures
|
|
90
|
+
* live in `RUN_ERROR_CODES` below as `RunSnapshot.error.code`.
|
|
91
|
+
*/
|
|
92
|
+
export const HTTP_ERROR_CODES = [
|
|
93
|
+
// Auth / access
|
|
94
|
+
'unauthenticated',
|
|
95
|
+
'forbidden',
|
|
96
|
+
'key_expired',
|
|
97
|
+
'key_revoked',
|
|
98
|
+
|
|
99
|
+
// Request / routing
|
|
100
|
+
'validation_error',
|
|
101
|
+
'not_found',
|
|
102
|
+
'rate_limited',
|
|
103
|
+
|
|
104
|
+
// Idempotency / run creation conflicts
|
|
105
|
+
'run_already_active',
|
|
106
|
+
'idempotency_in_flight',
|
|
107
|
+
'idempotency_key_mismatch',
|
|
108
|
+
|
|
109
|
+
// Streaming / protocol negotiation
|
|
110
|
+
'unsupported_stream_mode',
|
|
111
|
+
'force_engine_version_forbidden',
|
|
112
|
+
'mock_provider_forbidden',
|
|
113
|
+
|
|
114
|
+
// Capability / credential negotiation
|
|
115
|
+
'capability_not_provided',
|
|
116
|
+
'credential_required',
|
|
117
|
+
'credential_forbidden',
|
|
118
|
+
'credential_unavailable',
|
|
119
|
+
|
|
120
|
+
// HITL / interrupt callbacks
|
|
121
|
+
'interrupt_not_found',
|
|
122
|
+
'approval_token_invalid',
|
|
123
|
+
'approval_token_expired',
|
|
124
|
+
'approval_token_consumed',
|
|
125
|
+
|
|
126
|
+
// Generic server failure
|
|
127
|
+
'internal_error',
|
|
128
|
+
] as const;
|
|
129
|
+
|
|
130
|
+
export type HttpErrorCode = (typeof HTTP_ERROR_CODES)[number];
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Type guard that narrows a string to a known canonical HTTP error code.
|
|
134
|
+
* Returns false for host extensions and future spec additions; callers
|
|
135
|
+
* should still render a fallback from the envelope's `message`.
|
|
136
|
+
*/
|
|
137
|
+
export function isHttpErrorCode(value: unknown): value is HttpErrorCode {
|
|
138
|
+
return typeof value === 'string' && (HTTP_ERROR_CODES as readonly string[]).includes(value);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ─── Run error codes ─────────────────────────────────────────────────────
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Run-document error codes — stable identifiers used in
|
|
145
|
+
* `RunSnapshot.error.code` when a run reaches `failed`. These are distinct
|
|
146
|
+
* from the HTTP-level `ErrorEnvelope.error` codes above: a request can fail
|
|
147
|
+
* with `unauthenticated` before a run exists, while a run can fail later
|
|
148
|
+
* with `node_execution_failed` or `recursion_limit_exceeded`.
|
|
149
|
+
*
|
|
150
|
+
* Add a new code here when adding a new run failure mode that crosses the
|
|
151
|
+
* network boundary. Do NOT add codes for purely internal engine errors.
|
|
152
|
+
*
|
|
153
|
+
* **Drift risk note.** This list is canonical for SDK consumers but is
|
|
154
|
+
* NOT yet pinned by `conformance/src/scenarios/errors.test.ts` against
|
|
155
|
+
* host emission. A future conformance addition (tracked separately)
|
|
156
|
+
* SHOULD compare host-emitted error codes against this set and fail on
|
|
157
|
+
* unrecognized values. Until that lands, drift between this list + the
|
|
158
|
+
* engine's actual emission set is detectable only by manual review.
|
|
159
|
+
*/
|
|
160
|
+
export const RUN_ERROR_CODES = [
|
|
161
|
+
// Authorization / access
|
|
162
|
+
'auth_required',
|
|
163
|
+
'forbidden',
|
|
164
|
+
'workspace_not_found',
|
|
165
|
+
|
|
166
|
+
// Run-state conflicts
|
|
167
|
+
'run_already_active',
|
|
168
|
+
'run_not_found',
|
|
169
|
+
'run_terminal',
|
|
170
|
+
'engine_version_mismatch',
|
|
171
|
+
|
|
172
|
+
// Validation
|
|
173
|
+
'invalid_workflow_definition',
|
|
174
|
+
'invalid_trigger_input',
|
|
175
|
+
'node_type_not_found',
|
|
176
|
+
'config_validation_failed',
|
|
177
|
+
|
|
178
|
+
// Quota / budget
|
|
179
|
+
'token_budget_exceeded',
|
|
180
|
+
'concurrent_run_limit_reached',
|
|
181
|
+
'rate_limited',
|
|
182
|
+
|
|
183
|
+
// Execution
|
|
184
|
+
'node_timeout',
|
|
185
|
+
'global_timeout',
|
|
186
|
+
'node_execution_failed',
|
|
187
|
+
'external_call_failed',
|
|
188
|
+
'recursion_limit_exceeded',
|
|
189
|
+
'capability_not_provided',
|
|
190
|
+
|
|
191
|
+
// Approval
|
|
192
|
+
'approval_timeout',
|
|
193
|
+
'approval_token_invalid',
|
|
194
|
+
'approval_token_expired',
|
|
195
|
+
'approval_token_consumed',
|
|
196
|
+
|
|
197
|
+
// Persistence
|
|
198
|
+
'persistence_failed',
|
|
199
|
+
'doc_budget_exceeded',
|
|
200
|
+
] as const;
|
|
201
|
+
|
|
202
|
+
export type RunErrorCode = (typeof RUN_ERROR_CODES)[number];
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Type guard that narrows a string to a known {@link RunErrorCode}.
|
|
206
|
+
* Returns false for unknown / malformed values rather than throwing —
|
|
207
|
+
* SDK consumers usually want to display a fallback for unknown codes
|
|
208
|
+
* rather than crash the render pipeline.
|
|
209
|
+
*/
|
|
210
|
+
export function isRunErrorCode(value: unknown): value is RunErrorCode {
|
|
211
|
+
return typeof value === 'string' && (RUN_ERROR_CODES as readonly string[]).includes(value);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Run-level error shape — the structured `error` field on a `failed` run
|
|
216
|
+
* document. Distinct from the HTTP-level {@link import('./types.js').ErrorEnvelope}:
|
|
217
|
+
*
|
|
218
|
+
* - `RunError` lives on the run document; describes WHY the run failed.
|
|
219
|
+
* `code` is from the typed {@link RunErrorCode} vocabulary.
|
|
220
|
+
* - `ErrorEnvelope` lives on HTTP error responses; describes WHY the
|
|
221
|
+
* request failed. `error` is a free string code (often from
|
|
222
|
+
* {@link HTTP_ERROR_CODES}, but not type-pinned, to permit host
|
|
223
|
+
* extensions and future protocol additions).
|
|
224
|
+
*
|
|
225
|
+
* The shapes share `message` and `details` but diverge on the code field
|
|
226
|
+
* name (`code` vs `error`) by design — they represent different layers.
|
|
227
|
+
*/
|
|
228
|
+
export interface RunError {
|
|
229
|
+
code: RunErrorCode;
|
|
230
|
+
message: string;
|
|
231
|
+
/** Optional node-id where the error originated. */
|
|
232
|
+
nodeId?: string;
|
|
233
|
+
/** Optional structured context — implementations MAY extend. */
|
|
234
|
+
details?: Record<string, unknown>;
|
|
235
|
+
}
|
package/src/sse.ts
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SSE consumer for `GET /v1/runs/{runId}/events`. Async-iterable shape so
|
|
3
|
+
* consumers can write `for await (const event of client.runs.events(...))`.
|
|
4
|
+
*
|
|
5
|
+
* Implementation parses event:/data:/id: lines per RFC 8895. Native fetch +
|
|
6
|
+
* ReadableStream — zero third-party deps.
|
|
7
|
+
*
|
|
8
|
+
* Designed to be cancellable: pass an AbortSignal via options, or break out
|
|
9
|
+
* of the for-await loop and the underlying connection is torn down on the
|
|
10
|
+
* next tick.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import type { RunEventDoc, StreamMode } from './types.js';
|
|
14
|
+
|
|
15
|
+
export interface EventsStreamOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Single mode (e.g., 'updates') OR array of modes (S4 mixed-mode,
|
|
18
|
+
* e.g., ['updates', 'messages']). Arrays serialize to a comma-
|
|
19
|
+
* separated `?streamMode=updates,messages` query.
|
|
20
|
+
*/
|
|
21
|
+
readonly streamMode?: StreamMode | readonly StreamMode[];
|
|
22
|
+
readonly lastEventId?: string;
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
/**
|
|
25
|
+
* S3 batching hint. When set, server batches events for up to N ms;
|
|
26
|
+
* the SDK transparently flattens batched arrays back into individual
|
|
27
|
+
* RunEventDoc yields, so consumers see the same per-event surface as
|
|
28
|
+
* unbuffered streams. Range 0..5000.
|
|
29
|
+
*/
|
|
30
|
+
readonly bufferMs?: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface EventsStreamContext {
|
|
34
|
+
readonly baseUrl: string;
|
|
35
|
+
readonly apiKey: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function* streamEvents(
|
|
39
|
+
ctx: EventsStreamContext,
|
|
40
|
+
runId: string,
|
|
41
|
+
opts: EventsStreamOptions = {},
|
|
42
|
+
): AsyncGenerator<RunEventDoc, void, void> {
|
|
43
|
+
const params = new URLSearchParams();
|
|
44
|
+
if (opts.streamMode) {
|
|
45
|
+
const modeParam: string =
|
|
46
|
+
typeof opts.streamMode === 'string' ? opts.streamMode : opts.streamMode.join(',');
|
|
47
|
+
params.set('streamMode', modeParam);
|
|
48
|
+
}
|
|
49
|
+
if (opts.bufferMs !== undefined) {
|
|
50
|
+
params.set('bufferMs', String(opts.bufferMs));
|
|
51
|
+
}
|
|
52
|
+
const qs = params.toString();
|
|
53
|
+
const url = `${ctx.baseUrl}/v1/runs/${encodeURIComponent(runId)}/events${qs ? `?${qs}` : ''}`;
|
|
54
|
+
|
|
55
|
+
const headers: Record<string, string> = {
|
|
56
|
+
Accept: 'text/event-stream',
|
|
57
|
+
Authorization: `Bearer ${ctx.apiKey}`,
|
|
58
|
+
'Cache-Control': 'no-cache',
|
|
59
|
+
};
|
|
60
|
+
if (opts.lastEventId) {
|
|
61
|
+
headers['Last-Event-ID'] = opts.lastEventId;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const internalAbort = new AbortController();
|
|
65
|
+
const externalSignal = opts.signal;
|
|
66
|
+
if (externalSignal) {
|
|
67
|
+
if (externalSignal.aborted) internalAbort.abort();
|
|
68
|
+
else externalSignal.addEventListener('abort', () => internalAbort.abort(), { once: true });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const res = await fetch(url, { method: 'GET', headers, signal: internalAbort.signal });
|
|
72
|
+
if (!res.ok || res.body === null) {
|
|
73
|
+
throw new Error(`SSE subscribe failed: HTTP ${res.status}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const reader = res.body.getReader();
|
|
77
|
+
const decoder = new TextDecoder('utf-8');
|
|
78
|
+
let buffer = '';
|
|
79
|
+
let pendingEvent = 'message';
|
|
80
|
+
let pendingData: string[] = [];
|
|
81
|
+
let pendingId: string | null = null;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Flush the buffered event. Returns an array of RunEventDoc:
|
|
85
|
+
* - 0 elements when the buffer is empty or non-JSON (skip).
|
|
86
|
+
* - 1 element for a normal `event: <type>` event.
|
|
87
|
+
* - N elements when the server batched per S3 — `event: batch` with
|
|
88
|
+
* `data:` as a JSON array of RunEventDoc.
|
|
89
|
+
*/
|
|
90
|
+
const flushAndYield = (): RunEventDoc[] => {
|
|
91
|
+
if (pendingData.length === 0) {
|
|
92
|
+
pendingEvent = 'message';
|
|
93
|
+
pendingId = null;
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const dataStr = pendingData.join('\n');
|
|
97
|
+
const eventType = pendingEvent;
|
|
98
|
+
pendingEvent = 'message';
|
|
99
|
+
pendingData = [];
|
|
100
|
+
pendingId = null;
|
|
101
|
+
try {
|
|
102
|
+
const parsed = JSON.parse(dataStr) as unknown;
|
|
103
|
+
// S3 batched envelope — `event: batch` carries an array of events.
|
|
104
|
+
if (eventType === 'batch' && Array.isArray(parsed)) {
|
|
105
|
+
return parsed as RunEventDoc[];
|
|
106
|
+
}
|
|
107
|
+
// Normal single-event payload.
|
|
108
|
+
return [parsed as RunEventDoc];
|
|
109
|
+
} catch {
|
|
110
|
+
// Skip non-JSON events (keep-alive payloads, vendor extensions).
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
while (true) {
|
|
117
|
+
const { done, value } = await reader.read();
|
|
118
|
+
if (done) break;
|
|
119
|
+
buffer += decoder.decode(value, { stream: true });
|
|
120
|
+
let nlIdx: number;
|
|
121
|
+
while ((nlIdx = buffer.indexOf('\n')) !== -1) {
|
|
122
|
+
const rawLine = buffer.slice(0, nlIdx).replace(/\r$/, '');
|
|
123
|
+
buffer = buffer.slice(nlIdx + 1);
|
|
124
|
+
|
|
125
|
+
if (rawLine === '') {
|
|
126
|
+
for (const event of flushAndYield()) yield event;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
if (rawLine.startsWith(':')) continue; // keep-alive comment
|
|
130
|
+
|
|
131
|
+
const colon = rawLine.indexOf(':');
|
|
132
|
+
const field = colon === -1 ? rawLine : rawLine.slice(0, colon);
|
|
133
|
+
const valueRaw = colon === -1 ? '' : rawLine.slice(colon + 1);
|
|
134
|
+
const fieldValue = valueRaw.startsWith(' ') ? valueRaw.slice(1) : valueRaw;
|
|
135
|
+
|
|
136
|
+
switch (field) {
|
|
137
|
+
case 'event':
|
|
138
|
+
pendingEvent = fieldValue;
|
|
139
|
+
break;
|
|
140
|
+
case 'data':
|
|
141
|
+
pendingData.push(fieldValue);
|
|
142
|
+
break;
|
|
143
|
+
case 'id':
|
|
144
|
+
pendingId = fieldValue;
|
|
145
|
+
break;
|
|
146
|
+
default:
|
|
147
|
+
break;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Flush any final unterminated event.
|
|
152
|
+
for (const final of flushAndYield()) yield final;
|
|
153
|
+
} finally {
|
|
154
|
+
try {
|
|
155
|
+
reader.releaseLock();
|
|
156
|
+
} catch {
|
|
157
|
+
// best-effort
|
|
158
|
+
}
|
|
159
|
+
if (!internalAbort.signal.aborted) internalAbort.abort();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Reference pendingEvent/pendingId so the linter doesn't flag them as
|
|
163
|
+
// unused; they're consumed via flushAndYield's closure but TS can't see
|
|
164
|
+
// that across a generator boundary.
|
|
165
|
+
void pendingEvent;
|
|
166
|
+
void pendingId;
|
|
167
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Request/response types for the openwop REST surface.
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `api/openapi.yaml` and the JSON Schemas in
|
|
5
|
+
* `schemas/`. Hand-authored rather than codegen'd — see
|
|
6
|
+
* SDK README §rationale.
|
|
7
|
+
*
|
|
8
|
+
* Forward-compat: types use `string` (not narrow unions) for fields whose
|
|
9
|
+
* spec'd values may grow over time (status enums, event types, error codes).
|
|
10
|
+
* Consumers wanting exhaustive narrowing should `as const` their checks
|
|
11
|
+
* rather than relying on the SDK to refuse unknown values.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Run statuses per `RunSnapshot.status` in OpenAPI. */
|
|
15
|
+
export type RunStatus =
|
|
16
|
+
| 'pending'
|
|
17
|
+
| 'running'
|
|
18
|
+
| 'paused'
|
|
19
|
+
| 'waiting-approval'
|
|
20
|
+
| 'waiting-input'
|
|
21
|
+
| 'completed'
|
|
22
|
+
| 'failed'
|
|
23
|
+
| 'cancelled';
|
|
24
|
+
|
|
25
|
+
export interface Capabilities {
|
|
26
|
+
protocolVersion: string;
|
|
27
|
+
supportedEnvelopes: readonly string[];
|
|
28
|
+
schemaVersions: Record<string, number>;
|
|
29
|
+
limits: {
|
|
30
|
+
clarificationRounds: number;
|
|
31
|
+
schemaRounds: number;
|
|
32
|
+
envelopesPerTurn: number;
|
|
33
|
+
maxNodeExecutions?: number;
|
|
34
|
+
};
|
|
35
|
+
extensions?: Record<string, unknown>;
|
|
36
|
+
// Network-handshake superset (all `(future)` fields per capabilities.md)
|
|
37
|
+
implementation?: { name?: string; version?: string; vendor?: string };
|
|
38
|
+
engineVersion?: number;
|
|
39
|
+
eventLogSchemaVersion?: number;
|
|
40
|
+
supportedTransports?: readonly ('rest' | 'mcp' | 'a2a' | 'grpc')[];
|
|
41
|
+
configurable?: Record<string, unknown>;
|
|
42
|
+
observability?: Record<string, unknown>;
|
|
43
|
+
minClientVersion?: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RunSnapshot {
|
|
47
|
+
runId: string;
|
|
48
|
+
workflowId: string;
|
|
49
|
+
status: RunStatus;
|
|
50
|
+
currentNodeId?: string;
|
|
51
|
+
startedAt?: string;
|
|
52
|
+
completedAt?: string;
|
|
53
|
+
nodeStates?: Record<string, unknown>;
|
|
54
|
+
variables?: Record<string, unknown>;
|
|
55
|
+
channels?: Record<string, unknown>;
|
|
56
|
+
error?: { code?: string; message?: string };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Per-run parameter overlay carried in `RunOptions.configurable`. Reserved
|
|
61
|
+
* keys are typed; unknown keys are passed through verbatim. See
|
|
62
|
+
* `run-options.md`.
|
|
63
|
+
*/
|
|
64
|
+
export interface RunConfigurable {
|
|
65
|
+
/** Override the per-run node-execution ceiling. Clamped server-side. */
|
|
66
|
+
recursionLimit?: number;
|
|
67
|
+
/** Override AI model for nodes that consume `ctx.config.configurable.model`. */
|
|
68
|
+
model?: string;
|
|
69
|
+
/** Override AI temperature (server SHOULD enforce 0..2). */
|
|
70
|
+
temperature?: number;
|
|
71
|
+
/** Override AI max-tokens cap. */
|
|
72
|
+
maxTokens?: number;
|
|
73
|
+
/** Per-prompt-ID variant override map. */
|
|
74
|
+
promptOverrides?: Record<string, string>;
|
|
75
|
+
/** Implementation-specific extensions; passed through verbatim. */
|
|
76
|
+
[key: string]: unknown;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface CreateRunRequest {
|
|
80
|
+
workflowId: string;
|
|
81
|
+
inputs?: Record<string, unknown>;
|
|
82
|
+
tenantId?: string;
|
|
83
|
+
scopeId?: string;
|
|
84
|
+
callbackUrl?: string;
|
|
85
|
+
configurable?: RunConfigurable;
|
|
86
|
+
tags?: readonly string[];
|
|
87
|
+
metadata?: Record<string, unknown>;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CreateRunResponse {
|
|
91
|
+
runId: string;
|
|
92
|
+
status: RunStatus;
|
|
93
|
+
eventsUrl: string;
|
|
94
|
+
statusUrl?: string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface CancelRunRequest {
|
|
98
|
+
reason?: string;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export interface CancelRunResponse {
|
|
102
|
+
runId: string;
|
|
103
|
+
status: 'cancelled' | 'cancelling';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export interface ForkRunRequest {
|
|
107
|
+
fromSeq: number;
|
|
108
|
+
mode: 'replay' | 'branch';
|
|
109
|
+
runOptionsOverlay?: Record<string, unknown>;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export interface ForkRunResponse {
|
|
113
|
+
runId: string;
|
|
114
|
+
sourceRunId: string;
|
|
115
|
+
fromSeq?: number;
|
|
116
|
+
mode: 'replay' | 'branch';
|
|
117
|
+
status: RunStatus;
|
|
118
|
+
eventsUrl: string;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export interface ResolveInterruptRequest {
|
|
122
|
+
resumeValue: unknown;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export interface ResolveInterruptResponse {
|
|
126
|
+
runId: string;
|
|
127
|
+
nodeId: string;
|
|
128
|
+
status: RunStatus;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Token-scoped interrupt inspection response — mirrors `suspend-request.schema.json`
|
|
133
|
+
* (the `InterruptPayload` shape).
|
|
134
|
+
*/
|
|
135
|
+
export interface InterruptByTokenInspection {
|
|
136
|
+
kind: 'approval' | 'clarification' | 'external-event' | 'custom';
|
|
137
|
+
key: string;
|
|
138
|
+
resumeSchema?: Record<string, unknown>;
|
|
139
|
+
timeoutMs?: number;
|
|
140
|
+
data: unknown;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export interface ResolveInterruptByTokenResponse {
|
|
144
|
+
// Server-defined shape (openapi declares `type: object`); kept as
|
|
145
|
+
// unknown-typed object so SDK consumers narrow per implementation.
|
|
146
|
+
[key: string]: unknown;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface PollEventsResponse {
|
|
150
|
+
events: readonly RunEventDoc[];
|
|
151
|
+
isComplete: boolean;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Mirror of `run-event.schema.json` — top-level shape only. */
|
|
155
|
+
export interface RunEventDoc {
|
|
156
|
+
eventId: string;
|
|
157
|
+
runId: string;
|
|
158
|
+
nodeId?: string;
|
|
159
|
+
type: string; // RunEventType — string-typed for forward compat
|
|
160
|
+
payload: unknown;
|
|
161
|
+
timestamp: string;
|
|
162
|
+
sequence: number;
|
|
163
|
+
schemaVersion?: number;
|
|
164
|
+
engineVersion?: string;
|
|
165
|
+
causationId?: string;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export interface ErrorEnvelope {
|
|
169
|
+
error: string;
|
|
170
|
+
message: string;
|
|
171
|
+
details?: Record<string, unknown>;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
export type StreamMode = 'values' | 'updates' | 'messages' | 'debug';
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Thrown when the server returns a non-2xx response. Carries the original
|
|
178
|
+
* status, parsed error envelope (if available), the raw response text,
|
|
179
|
+
* and any `traceparent` the server returned (per
|
|
180
|
+
* `observability.md` §Trace context propagation —
|
|
181
|
+
* "Clients SHOULD display the trace ID in error messages so operators
|
|
182
|
+
* can search backend traces").
|
|
183
|
+
*/
|
|
184
|
+
export class WopError extends Error {
|
|
185
|
+
readonly status: number;
|
|
186
|
+
readonly envelope: ErrorEnvelope | undefined;
|
|
187
|
+
readonly rawText: string;
|
|
188
|
+
/** W3C `traceparent` from the response headers, when present. */
|
|
189
|
+
readonly traceparent: string | undefined;
|
|
190
|
+
/** 32-hex-char trace ID extracted from `traceparent`, when parseable. */
|
|
191
|
+
readonly traceId: string | undefined;
|
|
192
|
+
|
|
193
|
+
constructor(
|
|
194
|
+
status: number,
|
|
195
|
+
rawText: string,
|
|
196
|
+
envelope: ErrorEnvelope | undefined,
|
|
197
|
+
traceparent: string | undefined,
|
|
198
|
+
) {
|
|
199
|
+
const traceId = traceparent ? extractTraceId(traceparent) : undefined;
|
|
200
|
+
const baseMessage = envelope?.message ?? `openwop request failed: HTTP ${status}`;
|
|
201
|
+
const messageWithTrace = traceId ? `${baseMessage} (trace=${traceId})` : baseMessage;
|
|
202
|
+
super(messageWithTrace);
|
|
203
|
+
this.name = 'WopError';
|
|
204
|
+
this.status = status;
|
|
205
|
+
this.rawText = rawText;
|
|
206
|
+
this.envelope = envelope;
|
|
207
|
+
this.traceparent = traceparent;
|
|
208
|
+
this.traceId = traceId;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Extract the 32-hex trace ID from a W3C traceparent header. Format:
|
|
214
|
+
* `00-<32-hex>-<16-hex>-<2-hex>`. Returns undefined for malformed
|
|
215
|
+
* input — never throws (errors during error construction would be
|
|
216
|
+
* truly miserable).
|
|
217
|
+
*/
|
|
218
|
+
function extractTraceId(traceparent: string): string | undefined {
|
|
219
|
+
const parts = traceparent.split('-');
|
|
220
|
+
if (parts.length < 3) return undefined;
|
|
221
|
+
const traceId = parts[1];
|
|
222
|
+
if (!traceId || !/^[0-9a-f]{32}$/i.test(traceId)) return undefined;
|
|
223
|
+
return traceId;
|
|
224
|
+
}
|