@apicircle/core 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.
@@ -0,0 +1,1801 @@
1
+ import { BodyType, RequestAuth, Request, RequestRun, Folder, RequestBody, HttpMethod, EnvironmentVariable, Assertion, ContextExtraction, WorkspaceSynced, LinkedSnapshot, RequestOverride, EnvironmentVariableOverride, ExecutionPlan, PlanRun } from '@apicircle/shared';
2
+ import { W as WorkspaceState, a as WorkspacePatch } from './patches-N7mvDpXn.cjs';
3
+ export { b as WorkspacePatchKind } from './patches-N7mvDpXn.cjs';
4
+
5
+ type HeaderEntry$1 = {
6
+ key: string;
7
+ value: string;
8
+ enabled: boolean;
9
+ };
10
+ declare function getContentTypeForBodyType(bodyType: BodyType): string | null;
11
+ /**
12
+ * Reverse-map a Content-Type header value to a BodyType, or null if it
13
+ * doesn't match any known type. Strips parameters (e.g. `;charset=utf-8`)
14
+ * and is case-insensitive.
15
+ */
16
+ declare function getBodyTypeForContentType(contentType: string): BodyType | null;
17
+ /**
18
+ * Apply (or remove) the Content-Type header on a header list to match the
19
+ * given body type. Pure — returns a new array.
20
+ *
21
+ * - bodyType=none → strips any existing Content-Type entry.
22
+ * - existing Content-Type entry → updated value, preserving order.
23
+ * - no Content-Type entry → appended.
24
+ */
25
+ declare function applyContentTypeForBodyType(headers: HeaderEntry$1[], bodyType: BodyType): HeaderEntry$1[];
26
+
27
+ /**
28
+ * HTTP Headers dictionary — used for autocomplete in the Headers editor.
29
+ *
30
+ * Each entry maps a header name to its known values (empty array = free text).
31
+ * Values that depend on context (e.g. specific MIME types) provide a curated
32
+ * set of the most common options.
33
+ */
34
+ interface HeaderEntry {
35
+ name: string;
36
+ description: string;
37
+ values: string[];
38
+ /**
39
+ * `browser` — Forbidden by the Fetch spec; browsers silently ignore any
40
+ * attempt to set this header from JavaScript. On Desktop (native
41
+ * HTTP layer) the restriction does NOT apply — the header can
42
+ * still be set manually.
43
+ *
44
+ * `app` — Automatically injected by APICircle Studio at send-time (see
45
+ * autoHeaders.ts). Users can override these in the Headers tab
46
+ * and their value will take precedence.
47
+ *
48
+ * Omitted — Fully user-controlled on both Web and Desktop.
49
+ */
50
+ reserved?: 'browser' | 'app';
51
+ /** Short note shown in the autocomplete to explain the reservation. */
52
+ reservedNote?: string;
53
+ }
54
+ declare const HTTP_HEADERS_MAP: HeaderEntry[];
55
+ type HeaderSuggestionMode = 'request' | 'response';
56
+ /**
57
+ * Suggest header names by case-insensitive prefix. Empty prefix returns the
58
+ * full suggestable list; auto-fed (`reserved: 'app'`) headers are excluded.
59
+ * An optional `limit` caps the number of filtered (non-empty prefix) results.
60
+ *
61
+ * `mode` filters by whether the header is request- or response-side
62
+ * relevant. Defaults to `'request'` for back-compat with the request
63
+ * editor's existing call sites.
64
+ */
65
+ declare function suggestHeaders(prefix: string, limit?: number, mode?: HeaderSuggestionMode): HeaderEntry[];
66
+ /**
67
+ * Get the known values for a specific header name (case-insensitive).
68
+ * Returns an empty array when the header has no predefined values (free text).
69
+ */
70
+ declare function getHeaderValues(headerName: string): string[];
71
+ /**
72
+ * Returns the HeaderEntry for an exact name match, or undefined.
73
+ */
74
+ declare function getHeaderEntry(headerName: string): HeaderEntry | undefined;
75
+
76
+ interface AuthApplyTarget {
77
+ url: string;
78
+ method: string;
79
+ headers: Record<string, string>;
80
+ body: BodyInit | null;
81
+ }
82
+ interface AuthApplyOptions {
83
+ /**
84
+ * Called when applyAuth refreshes an expired OAuth2 access token. The
85
+ * store wires this to persist the new accessToken / refreshToken /
86
+ * expiresAt onto the request's auth payload — without it the refresh
87
+ * works for THIS request but the next request would re-refresh because
88
+ * the in-memory state didn't catch up.
89
+ */
90
+ onTokenRefreshed?: (auth: Extract<RequestAuth, {
91
+ type: 'oauth2-client-credentials';
92
+ } | {
93
+ type: 'oauth2-auth-code';
94
+ } | {
95
+ type: 'oauth2-pkce';
96
+ } | {
97
+ type: 'oauth2-password';
98
+ } | {
99
+ type: 'oauth2-implicit';
100
+ } | {
101
+ type: 'oauth2-device';
102
+ }>, next: {
103
+ accessToken: string;
104
+ tokenType: string;
105
+ refreshToken?: string;
106
+ expiresAt: number;
107
+ obtainedScope?: string;
108
+ }) => void | Promise<void>;
109
+ /**
110
+ * Test seam for the refresh fetch. When omitted, the global fetch is
111
+ * used (matching production behavior).
112
+ */
113
+ fetchImpl?: typeof fetch;
114
+ /**
115
+ * Refresh tokens when they have less than this many milliseconds left.
116
+ * Default: 60_000 (1 min). Set to 0 to disable proactive refresh.
117
+ */
118
+ refreshLeewayMs?: number;
119
+ }
120
+ interface AuthApplyResult {
121
+ url: string;
122
+ headers: Record<string, string>;
123
+ /**
124
+ * Non-fatal warnings raised while applying auth (bad JWT key, malformed
125
+ * payload JSON, etc). The request still goes out — usually unauthenticated
126
+ * — but executeRequest surfaces these to the user so they don't see a
127
+ * mysterious 401 with no clue why their auth config didn't apply.
128
+ */
129
+ warnings?: AuthApplyWarning[];
130
+ }
131
+ interface AuthApplyWarning {
132
+ /** Stable code so callers can match without parsing message strings. */
133
+ code: 'jwt-payload-json-invalid' | 'jwt-headers-json-invalid' | 'jwt-sign-failed' | 'hawk-url-invalid' | 'oauth2-refresh-failed';
134
+ /** Human-readable message — safe to surface in the UI. */
135
+ message: string;
136
+ }
137
+ declare function applyAuth(target: AuthApplyTarget, auth: RequestAuth, opts?: AuthApplyOptions): Promise<AuthApplyResult>;
138
+
139
+ /**
140
+ * Auto-fed request headers — platform-specific, non-editable, not stored.
141
+ *
142
+ * These headers are injected at send-time by the execution engine:
143
+ * • They are NEVER written to IndexedDB or Git remote.
144
+ * • A new X-Trace-Span-Id and traceparent are generated for every send.
145
+ * • They do NOT appear in the user-visible "Headers" editor tab — the
146
+ * HeadersTab "Auto-fed at send" aside lists them for reference only.
147
+ *
148
+ * User-set headers always win: if the user typed `X-Client-Version` into the
149
+ * Headers tab, the auto value is suppressed.
150
+ */
151
+ /** Canonical desktop origin used in requests (also used by the native HTTP layer). */
152
+ declare const DESKTOP_APP_ORIGIN = "http://app.studio.apicircle.dev";
153
+ /**
154
+ * Generate a random hex span-id (16 hex chars = 64 bits, compatible with
155
+ * W3C Trace Context `traceparent` and OpenTelemetry span-id format).
156
+ */
157
+ declare function generateSpanId(): string;
158
+ /**
159
+ * Generate a W3C traceparent header value.
160
+ * Format: 00-<trace-id (32 hex)>-<span-id (16 hex)>-01
161
+ */
162
+ declare function generateTraceParent(): string;
163
+ /**
164
+ * Override hooks for tests — let test code feed deterministic values
165
+ * for span-id / traceparent / platform without monkey-patching `crypto`.
166
+ * Production code never passes these.
167
+ */
168
+ interface AutoHeaderOverrides {
169
+ spanId?: string;
170
+ traceparent?: string;
171
+ platform?: 'desktop' | 'web';
172
+ version?: string;
173
+ name?: string;
174
+ }
175
+ /**
176
+ * Returns the set of auto-fed headers for a single request execution.
177
+ * Call this once per send — `X-Trace-Span-Id` and `traceparent` are
178
+ * re-generated on every invocation.
179
+ *
180
+ * @param userHeaders The headers the user has configured so auto-headers
181
+ * do NOT override anything the user has explicitly set.
182
+ * @param overrides Test-only override hooks; never set in production.
183
+ */
184
+ declare function buildAutoHeaders(userHeaders: Record<string, string>, overrides?: AutoHeaderOverrides): Record<string, string>;
185
+ /**
186
+ * Merge auto-headers into user headers. User-defined values always win —
187
+ * auto-headers only fill gaps.
188
+ */
189
+ declare function mergeWithAutoHeaders(userHeaders: Record<string, string>, overrides?: AutoHeaderOverrides): Record<string, string>;
190
+
191
+ interface BuiltRequest {
192
+ url: string;
193
+ method: string;
194
+ headers: Record<string, string>;
195
+ body: BodyInit | null;
196
+ /**
197
+ * Non-fatal warnings raised by applyAuth (bad JWT key, malformed
198
+ * payload JSON, etc). executeRequest forwards these into
199
+ * `ExecutionResult.authWarnings` so the UI can surface them alongside
200
+ * the response — without them, a misconfigured JWT silently produces
201
+ * a 401 with no clue why.
202
+ */
203
+ authWarnings: AuthApplyWarning[];
204
+ }
205
+ /**
206
+ * Resolves an attachment slotId to a Blob (with filename for form-data).
207
+ * The host (UI layer) reads this from its IndexedDB attachments store.
208
+ * Returns null when the attachment is missing — composeBody treats missing
209
+ * attachments as empty fields rather than throwing.
210
+ */
211
+ type AttachmentResolver = (slotId: string) => Promise<{
212
+ blob: Blob;
213
+ filename: string;
214
+ } | null>;
215
+ /**
216
+ * Split a typed URL into the base part (everything before `?`) and a
217
+ * structured query-row list. Used by the editor's URL input to keep the
218
+ * URL field and the Query Params sub-tab in sync — when the user types or
219
+ * pastes `?key=val`, the rows surface in the Params list automatically.
220
+ *
221
+ * Variable references like `{{NAME}}` in keys or values are preserved
222
+ * verbatim — they stay templated and resolve at send time. We use
223
+ * permissive splitting (split on `&` then on the first `=`) rather than
224
+ * `URLSearchParams` because URLSearchParams percent-decodes `{{` into `{{`
225
+ * fine but also collapses whitespace and strips empty keys, which fights
226
+ * the user's intent during in-progress typing.
227
+ */
228
+ declare function parseUrlQuery(rawUrl: string): {
229
+ base: string;
230
+ query: Array<{
231
+ key: string;
232
+ value: string;
233
+ enabled: boolean;
234
+ }>;
235
+ };
236
+ /**
237
+ * Inverse of `parseUrlQuery`. Renders the structured query rows back into a
238
+ * URL-bar-friendly string, skipping disabled or empty-key rows. Values are
239
+ * left as-is (no double-encoding) — `composeUrl` runs at send time and
240
+ * handles wire encoding properly.
241
+ */
242
+ declare function composeUrlWithQuery(base: string, query: ReadonlyArray<{
243
+ key: string;
244
+ value: string;
245
+ enabled: boolean;
246
+ }>): string;
247
+ /** Extract the placeholder names appearing in a URL, in document order, deduped. */
248
+ declare function findPathPlaceholders(rawUrl: string): string[];
249
+ /**
250
+ * Substitute `:name` and `{name}` placeholders in a URL's path with the
251
+ * matching values from `pathParams`. The query string passes through
252
+ * untouched — a `{{var}}` in there is a variable reference, not a path
253
+ * placeholder. Missing keys substitute to empty string; the Editor surfaces
254
+ * unbound placeholders as a UI warning, not a runtime error.
255
+ */
256
+ declare function applyPathParams(rawUrl: string, pathParams: Record<string, string>): string;
257
+ /**
258
+ * Build a `Cookie` header value from a list of name/value pairs. Skips
259
+ * disabled or empty-key rows. Returns the empty string when nothing applies.
260
+ * Per RFC 6265, cookie values may not contain CTL chars or `;` (which is the
261
+ * row separator) — we strip CTLs and also strip `;` from values to keep the
262
+ * row framing intact when a {{var}} resolves to something containing one.
263
+ */
264
+ declare function composeCookieHeader(rows: ReadonlyArray<{
265
+ key: string;
266
+ value: string;
267
+ enabled: boolean;
268
+ }>): string;
269
+ declare function composeUrl(rawUrl: string, params: ReadonlyArray<{
270
+ key: string;
271
+ value: string;
272
+ enabled: boolean;
273
+ }>): string;
274
+ declare function composeHeaders(rows: ReadonlyArray<{
275
+ key: string;
276
+ value: string;
277
+ enabled: boolean;
278
+ }>): Record<string, string>;
279
+ /**
280
+ * Serialize a request body for fetch(). Async because form-data and binary
281
+ * may need to read attachment blobs from the host's storage layer.
282
+ */
283
+ declare function composeBody(body: Request['body'], resolveAttachment?: AttachmentResolver): Promise<BodyInit | null>;
284
+ interface BuildRequestOptions {
285
+ resolveAttachment?: AttachmentResolver;
286
+ /**
287
+ * applyAuth options — most importantly the `onTokenRefreshed`
288
+ * callback that lets the store persist refreshed OAuth2 tokens.
289
+ * Forwarded as-is.
290
+ */
291
+ authOptions?: AuthApplyOptions;
292
+ /**
293
+ * Test-only override hooks for the auto-fed headers. Lets specs feed
294
+ * deterministic values for `X-Trace-Span-Id`, `traceparent`, and
295
+ * `X-Client-Platform`. Production callers omit this.
296
+ */
297
+ autoHeaderOverrides?: AutoHeaderOverrides;
298
+ }
299
+ declare function buildRequest(req: Request, opts?: BuildRequestOptions): Promise<BuiltRequest>;
300
+
301
+ /**
302
+ * Detect whether the host is the APICircle Studio Desktop shell.
303
+ *
304
+ * The Electron preload script attaches a bridge object on `globalThis.apicircleDesktop`
305
+ * (see `apps/desktop/src/main/preload.ts`). The web app exposes nothing, so a
306
+ * presence check is sufficient — we don't need to inspect the bridge's shape.
307
+ */
308
+ declare function isDesktop(): boolean;
309
+
310
+ interface ResolutionScope {
311
+ contextVars: Record<string, string>;
312
+ activeEnv: Record<string, string>;
313
+ priorityEnvs: Array<Record<string, string>>;
314
+ secrets: Record<string, string>;
315
+ }
316
+ interface ResolveResult {
317
+ value: string;
318
+ /** Names that were referenced but not found in any scope. */
319
+ missing: string[];
320
+ }
321
+ declare function lookup(scope: ResolutionScope, name: string): string | undefined;
322
+ /**
323
+ * Replace every `{{NAME}}` in `input` with its resolved value. Unknown names
324
+ * are left as-is in the output (so the user can see which placeholder didn't
325
+ * resolve) and reported via `missing`.
326
+ */
327
+ declare function resolveString(input: string, scope: ResolutionScope): ResolveResult;
328
+ /**
329
+ * Resolve placeholders in every string value of an object (one level deep).
330
+ * Used for header / param row arrays — both keys and values are resolved.
331
+ * Returns a flat list of all missing names across the inputs.
332
+ */
333
+ declare function resolveStringMap(obj: Record<string, string>, scope: ResolutionScope): {
334
+ result: Record<string, string>;
335
+ missing: string[];
336
+ };
337
+ /**
338
+ * Build a ResolutionScope from a Workspace + a per-request context-vars
339
+ * list. Optional plan-level priority overrides take precedence over the
340
+ * workspace's global priority order.
341
+ *
342
+ * `secrets` is passed in already-decrypted because resolveString runs
343
+ * synchronously — decryption happens once before send.
344
+ */
345
+ declare function buildScope(args: {
346
+ contextVars: ReadonlyArray<{
347
+ key: string;
348
+ value: string;
349
+ }>;
350
+ environments: Record<string, Record<string, string>>;
351
+ activeEnvName: string | null;
352
+ priorityOrder: string[];
353
+ secrets?: Record<string, string>;
354
+ }): ResolutionScope;
355
+ type VariableSource = 'context' | 'active-env' | 'priority-env' | 'secret';
356
+ interface VariableSuggestion {
357
+ key: string;
358
+ source: VariableSource;
359
+ /** Display-only — secrets always show as the placeholder string. */
360
+ preview: string;
361
+ }
362
+ /**
363
+ * Walk a ResolutionScope in precedence order and produce one suggestion
364
+ * per unique key. Used by the editor's `{{` autocomplete and Monaco's
365
+ * completion provider.
366
+ */
367
+ declare function collectVariableSuggestions(scope: ResolutionScope): VariableSuggestion[];
368
+ /**
369
+ * Suggestions to show given a cursor position inside a text field. Returns
370
+ * `null` when the cursor isn't inside an open `{{ … }}` token, so callers
371
+ * can hide the popup. When inside a token, returns matches filtered by the
372
+ * partial token text.
373
+ */
374
+ declare function getVariableAutocomplete(text: string, cursorPosition: number, scope: ResolutionScope): VariableSuggestion[] | null;
375
+
376
+ interface PreSendWarning {
377
+ kind: 'unresolved-variable' | 'unbound-path-param' | 'content-type-mismatch' | 'url-embedded-credentials';
378
+ message: string;
379
+ }
380
+ interface PreSendBlocker {
381
+ kind: 'auth-fields-missing' | 'unparseable-url' | 'empty-url';
382
+ message: string;
383
+ }
384
+ interface PreSendValidationResult {
385
+ warnings: PreSendWarning[];
386
+ blockers: PreSendBlocker[];
387
+ }
388
+ /** Inputs to the validator — the request + resolution scope. */
389
+ interface PreSendValidationInput {
390
+ request: Request;
391
+ scope: ResolutionScope;
392
+ }
393
+ declare function preSendValidation({ request, scope, }: PreSendValidationInput): PreSendValidationResult;
394
+
395
+ interface ExecutionResult {
396
+ startedAt: string;
397
+ durationMs: number;
398
+ status: number | null;
399
+ ok: boolean;
400
+ statusText: string;
401
+ headers: Record<string, string>;
402
+ body: string;
403
+ bodyKind: 'json' | 'text' | 'binary' | 'empty';
404
+ error?: string;
405
+ url: string;
406
+ method: string;
407
+ /**
408
+ * Non-fatal warnings from applyAuth (e.g. malformed JWT key, bad
409
+ * payload JSON). Empty array when auth applied cleanly. UI surfaces
410
+ * these alongside the response so users can see WHY their request
411
+ * went out unauthenticated.
412
+ */
413
+ authWarnings: AuthApplyWarning[];
414
+ /**
415
+ * True when the response body exceeded `MAX_RESPONSE_BODY_BYTES` and we
416
+ * stopped reading. The `body` field still contains the prefix we did
417
+ * read — callers should render a "Response truncated at X MB" badge.
418
+ * Omitted when the body was read in full.
419
+ */
420
+ responseTruncated?: boolean;
421
+ }
422
+ interface ExecuteOptions {
423
+ fetchImpl?: typeof fetch;
424
+ timeoutMs?: number | null;
425
+ signal?: AbortSignal;
426
+ resolveAttachment?: AttachmentResolver;
427
+ /**
428
+ * applyAuth options — `onTokenRefreshed` is the important one for
429
+ * production: the store wires it to persist refreshed OAuth2 tokens
430
+ * back into `RequestAuth` so subsequent sends see the new state.
431
+ */
432
+ authOptions?: AuthApplyOptions;
433
+ /**
434
+ * Test-only override hooks for the auto-fed headers. Lets specs feed
435
+ * deterministic values for `X-Trace-Span-Id`, `traceparent`, etc.
436
+ * Production callers omit this.
437
+ */
438
+ autoHeaderOverrides?: AutoHeaderOverrides;
439
+ }
440
+ /**
441
+ * Execute a request through the browser's fetch (or an injected impl for
442
+ * tests). Returns a flat ExecutionResult — never throws for HTTP errors.
443
+ * Network failures and timeouts are captured into result.error with status=null.
444
+ *
445
+ * Challenge-driven auth (Digest, NTLM) is handled here: when the first
446
+ * fetch returns 401 with a recognized `WWW-Authenticate` scheme, we
447
+ * compute the response header and re-fetch once. NTLM further requires a
448
+ * second retry to send the Type-3 Authenticate after the Type-2
449
+ * Challenge — that's transparent to the caller, the returned result
450
+ * reflects the FINAL response.
451
+ */
452
+ declare function executeRequest(req: Request, opts?: ExecuteOptions): Promise<ExecutionResult>;
453
+
454
+ /**
455
+ * Adapt a stored `RequestRun` into the `ExecutionResult` shape that
456
+ * `ResponseViewer` consumes. `RequestRun` is the persisted, post-mortem
457
+ * record (kept on `WorkspaceLocal.history` and capped); `ExecutionResult`
458
+ * is the live in-flight value the editor receives. Both expose the same
459
+ * conceptual fields, so the History and Execution detail views can lean
460
+ * on the same renderer instead of forking the layout.
461
+ *
462
+ * The body is `responseBodyPreview` (already capped by
463
+ * `RUN_BODY_PREVIEW_LIMIT`); the editor will show a truncation hint when
464
+ * `RequestRun.responseTruncated` is true.
465
+ */
466
+ declare function requestRunToExecutionResult(run: RequestRun): ExecutionResult;
467
+
468
+ /**
469
+ * HTTP Digest Access Authentication (RFC 7616, supersedes RFC 2617).
470
+ *
471
+ * Digest is a challenge-response scheme: the client makes a request, the
472
+ * server responds 401 with `WWW-Authenticate: Digest ...`, and the client
473
+ * retries with `Authorization: Digest ...` carrying a hash of the
474
+ * credentials + the server-supplied nonce. This module owns the parsing
475
+ * of the challenge directives and the construction of the response
476
+ * header. The challenge round-trip itself lives in `executeRequest` —
477
+ * it's the engine's job to fire the first request, see the 401, call
478
+ * back here, and re-send.
479
+ *
480
+ * Algorithm support: MD5 (default), MD5-sess, SHA-256, SHA-256-sess,
481
+ * SHA-512-256, and the `-sess` variants. MD5 is required for interop
482
+ * with the long tail of legacy servers; the implementation is in the
483
+ * private `_legacyHashes` module so the rest of the codebase doesn't
484
+ * grow new MD5 callsites.
485
+ */
486
+ interface DigestChallenge {
487
+ realm: string;
488
+ nonce: string;
489
+ /** Comma-separated list per RFC 7616; the first one we recognize wins. */
490
+ qop?: string;
491
+ opaque?: string;
492
+ algorithm?: string;
493
+ /** Server-supplied opaque token returned verbatim in the response. */
494
+ domain?: string;
495
+ /** Stale=true means the previous nonce expired but credentials are still valid. */
496
+ stale?: string;
497
+ /** Any directives we don't model end up here for diagnostics. */
498
+ [extra: string]: string | undefined;
499
+ }
500
+ /**
501
+ * Parse the value of a `WWW-Authenticate: Digest ...` header into its
502
+ * directives. The header may appear with or without the `Digest ` prefix
503
+ * (callers sometimes strip it); both forms work.
504
+ *
505
+ * Quoted values have the surrounding quotes removed; unquoted values are
506
+ * taken as-is up to the next comma or whitespace. Unknown directives are
507
+ * preserved on the returned object so diagnostics can surface them.
508
+ */
509
+ declare function parseDigestChallenge(header: string): DigestChallenge;
510
+ interface BuildDigestArgs {
511
+ method: string;
512
+ /** Request URI as it appears on the wire (path + query, not the full URL). */
513
+ uri: string;
514
+ username: string;
515
+ password: string;
516
+ challenge: DigestChallenge;
517
+ /**
518
+ * Override for the client nonce. If omitted, 16 random hex chars are
519
+ * generated via `crypto.getRandomValues`. Tests pass a fixed value to
520
+ * make the resulting header deterministic.
521
+ */
522
+ cnonce?: string;
523
+ /**
524
+ * Nonce-count, per RFC 7616 §3.4. Each request reusing the same server
525
+ * nonce should bump nc; we default to 1 because the engine clears the
526
+ * stored challenge when the server rotates the nonce. 8 hex chars,
527
+ * lowercase.
528
+ */
529
+ nc?: string;
530
+ /**
531
+ * Body for `qop=auth-int` (entity-body hash). Ignored for `qop=auth`.
532
+ * Strings, `Uint8Array`, `ArrayBuffer`, and `Blob` are all hashable;
533
+ * if `null`/`undefined` we still emit auth-int but with the empty-body
534
+ * hash. FormData / ReadableStream callers should serialize first
535
+ * (auth-int requires the EXACT bytes the server will see).
536
+ */
537
+ entityBody?: string | Uint8Array | ArrayBuffer | Blob | null;
538
+ }
539
+ /**
540
+ * Build the `Authorization: Digest ...` header value (without the
541
+ * `Authorization:` prefix — caller prepends it).
542
+ *
543
+ * Returns a string ready to put on the wire. Throws when the challenge
544
+ * specifies an algorithm we don't support; the engine should surface
545
+ * that as a "this server's auth scheme isn't supported yet" error
546
+ * rather than retrying forever.
547
+ */
548
+ declare function buildDigestAuthHeader(args: BuildDigestArgs): Promise<string>;
549
+
550
+ /**
551
+ * Build the Type-1 Negotiate message, base64-encoded for use as
552
+ * `Authorization: NTLM <value>`. Sent by the client to advertise its
553
+ * capabilities and trigger the server's challenge response.
554
+ */
555
+ declare function buildNtlmType1Negotiate(domain: string, workstation: string): string;
556
+ interface NtlmType2Challenge {
557
+ /** 8-byte server challenge nonce. */
558
+ challenge: Uint8Array;
559
+ /**
560
+ * Variable-length AV_PAIR target-info block. Echoed verbatim in the
561
+ * Type-3 NTLMv2 blob so the server can verify the response.
562
+ */
563
+ targetInfo: Uint8Array;
564
+ /**
565
+ * Original raw bytes of the entire Type-2 message — required when the
566
+ * caller wants to compute a MIC over `Type1 || Type2 || Type3` per
567
+ * [MS-NLMP] §3.1.5.1.2. Set automatically by `parseNtlmType2Challenge`.
568
+ * Optional so callers constructing the challenge literal in tests can
569
+ * skip it; MIC computation needs the parser-produced form.
570
+ */
571
+ rawBytes?: Uint8Array;
572
+ }
573
+ /**
574
+ * Parse the Type-2 Challenge message extracted from the server's
575
+ * `WWW-Authenticate: NTLM <base64>` reply. Returns the server challenge
576
+ * + target info bytes that feed into Type-3 construction.
577
+ */
578
+ declare function parseNtlmType2Challenge(base64: string): NtlmType2Challenge;
579
+ interface BuildNtlmType3Args {
580
+ username: string;
581
+ password: string;
582
+ domain: string;
583
+ workstation: string;
584
+ challenge: NtlmType2Challenge;
585
+ /** Override for the 8-byte client challenge — tests pass a fixed value. */
586
+ clientChallenge?: Uint8Array;
587
+ /** Override for the timestamp (ms since epoch) — tests pass a fixed value. */
588
+ timestampMs?: number;
589
+ /**
590
+ * Raw bytes of the Type-1 Negotiate message we sent in the previous
591
+ * round. When BOTH this and `type2Message` are provided, we emit a
592
+ * 16-byte MIC at offset 72 of the Type-3 message and set MsvAvFlags
593
+ * bit 0x2 in the AV_PAIR target info — required by hardened Win Server
594
+ * 2019+ AD configurations per [MS-NLMP] §3.1.5.1.2. When either is
595
+ * absent we preserve the legacy "no MIC" layout for back-compat with
596
+ * older servers that reject the longer header.
597
+ */
598
+ type1Message?: Uint8Array;
599
+ /** Raw bytes of the Type-2 Challenge message — paired with `type1Message`. */
600
+ type2Message?: Uint8Array;
601
+ }
602
+ /**
603
+ * Build the Type-3 Authenticate message (NTLMv2). Computes the NTLMv2
604
+ * hash, the NTProofStr (HMAC-MD5 over server-challenge + blob), and
605
+ * packs everything into the binary message format. Returns base64 for
606
+ * use as `Authorization: NTLM <value>`.
607
+ */
608
+ declare function buildNtlmType3Authenticate(args: BuildNtlmType3Args): string;
609
+
610
+ /**
611
+ * Hawk authentication scheme (https://github.com/mozilla/hawk).
612
+ *
613
+ * Hawk uses an HMAC of a normalized request string keyed by a shared
614
+ * secret. Unlike Digest / NTLM there's no challenge round-trip — every
615
+ * request is signed independently using:
616
+ *
617
+ * normalized = "hawk.1.header\n{ts}\n{nonce}\n{METHOD}\n{path?query}\n
618
+ * {host-lc}\n{port}\n{payload-hash}\n{ext}\n"
619
+ * mac = base64(HMAC-{algorithm}(secret, normalized))
620
+ *
621
+ * We default to SHA-256 (the modern recommendation); SHA-1 is supported
622
+ * for interop with legacy Hawk servers. Body-payload hashing is a future
623
+ * extension — for now we always pass an empty payload-hash, which the
624
+ * server must verify by setting `Hash: ""` or skipping payload validation.
625
+ */
626
+ interface HawkSignArgs {
627
+ method: string;
628
+ /**
629
+ * Full request URL — we extract scheme/host/port/path/query from this
630
+ * since the normalized string needs lowercase host + numeric port.
631
+ */
632
+ url: string;
633
+ hawkId: string;
634
+ hawkKey: string;
635
+ algorithm?: 'sha256' | 'sha1';
636
+ /**
637
+ * Override for the timestamp (Unix seconds). Tests pass a fixed value;
638
+ * production callers either omit (use `Date.now()`) or apply
639
+ * `timestampOffset` to compensate for client-server clock skew.
640
+ */
641
+ timestamp?: number;
642
+ /**
643
+ * 8 hex chars by default; tests pass a fixed value. The server tracks
644
+ * (id, ts, nonce) tuples to detect replay, so even fixed tests are
645
+ * one-shot.
646
+ */
647
+ nonce?: string;
648
+ /** Optional `app=` and `dlg=` directives for delegated requests. */
649
+ app?: string;
650
+ delegation?: string;
651
+ /** Optional `ext=` directive for application-specific data. */
652
+ ext?: string;
653
+ /**
654
+ * Optional payload + content-type for body-binding. When present, the
655
+ * normalized request string includes a `hash=BASE64(H(payload))` line
656
+ * AND we emit a `hash="…"` directive in the Authorization header.
657
+ * Servers configured with body-binding reject requests that lack this;
658
+ * leaving the field undefined preserves the prior behavior (server
659
+ * must accept body-less signing).
660
+ */
661
+ payload?: {
662
+ body: string | ArrayBuffer | Uint8Array;
663
+ contentType: string;
664
+ };
665
+ }
666
+ declare function buildHawkAuthHeader(args: HawkSignArgs): Promise<string>;
667
+
668
+ /**
669
+ * AWS Signature Version 4 — request signing for AWS APIs.
670
+ *
671
+ * SigV4 is per-request HMAC chain over a canonical-request string. The
672
+ * server (or a STS-issued temporary credentials provider) verifies the
673
+ * signature using the same algorithm, so request integrity is end-to-end
674
+ * without any callback / handshake.
675
+ *
676
+ * canonical = METHOD\nPATH\nQUERY\nHEADERS\nSIGNED_HEADERS\nPAYLOAD_HASH
677
+ * stringToSign = "AWS4-HMAC-SHA256\n{amzDate}\n{credScope}\n{sha256(canonical)}"
678
+ * kSigning = HMAC(HMAC(HMAC(HMAC("AWS4{secret}", date), region), service), "aws4_request")
679
+ * signature = hex(HMAC(kSigning, stringToSign))
680
+ *
681
+ * Two delivery modes:
682
+ * - `header`: sets `Authorization: AWS4-HMAC-SHA256 Credential=..., SignedHeaders=..., Signature=...`
683
+ * - `query`: rewrites the URL with `X-Amz-*` query params (presigned URL).
684
+ *
685
+ * Body hashing only runs in `header` mode (presigned URLs always
686
+ * advertise `UNSIGNED-PAYLOAD`). Streaming / chunked bodies fall through
687
+ * to `UNSIGNED-PAYLOAD` because we'd otherwise have to buffer the entire
688
+ * stream before signing.
689
+ */
690
+ /**
691
+ * Body shapes we can hash for SigV4 payload signing. Anything we can't
692
+ * read as bytes synchronously goes to `UNSIGNED-PAYLOAD` — AWS accepts
693
+ * that as long as `x-amz-content-sha256: UNSIGNED-PAYLOAD` is in the
694
+ * signed headers. ReadableStream falls into this bucket since draining
695
+ * it before signing would consume the body before fetch can send it.
696
+ */
697
+ type SigV4Body = string | ArrayBuffer | Uint8Array | Blob | URLSearchParams | FormData | ReadableStream<Uint8Array> | null | undefined;
698
+ interface SigV4SignArgs {
699
+ method: string;
700
+ url: string;
701
+ /** Existing request headers — passed through and merged with SigV4 additions. */
702
+ headers: Record<string, string>;
703
+ /**
704
+ * Body for payload hashing. `string`, `ArrayBuffer`, `Uint8Array`,
705
+ * `Blob`, and `URLSearchParams` are hashed verbatim. `FormData` and
706
+ * `ReadableStream` fall through to `UNSIGNED-PAYLOAD` — AWS accepts
707
+ * that signing mode and many AWS SDKs default to it for streaming.
708
+ */
709
+ body?: SigV4Body;
710
+ accessKeyId: string;
711
+ secretAccessKey: string;
712
+ region: string;
713
+ service: string;
714
+ /** STS-issued session token; copied to `X-Amz-Security-Token`. */
715
+ sessionToken?: string;
716
+ /** Where to put the signature — header (default) or query (presigned URL). */
717
+ addTo?: 'header' | 'query';
718
+ /**
719
+ * S3 specifically does NOT collapse double slashes or normalize dot
720
+ * segments — it treats `bucket/foo//bar` as a different object from
721
+ * `bucket/foo/bar`. Set true when `service: 's3'` to preserve the
722
+ * raw path. Default false (matches non-S3 services like execute-api).
723
+ *
724
+ * If you don't pass this and `service === 's3'`, we auto-enable it —
725
+ * the common case for S3 callers is "preserve my path exactly".
726
+ */
727
+ preservePathSlashes?: boolean;
728
+ /**
729
+ * Override `now` for tests. The amz-date stamp must be the SAME instant
730
+ * used to build the credential scope, so we capture once.
731
+ */
732
+ now?: Date;
733
+ }
734
+ interface SigV4SignResult {
735
+ url: string;
736
+ headers: Record<string, string>;
737
+ }
738
+ declare function applyAwsSigV4(args: SigV4SignArgs): Promise<SigV4SignResult>;
739
+
740
+ /**
741
+ * JWT (RFC 7519) signing — Bearer token generation.
742
+ *
743
+ * This is the "self-signed assertion" variant of `jwt-bearer` auth: the
744
+ * client mints a JWT signed with its own key, then sends it as
745
+ * `Authorization: Bearer <jwt>`. Different from OAuth2 JWT-bearer flow
746
+ * (RFC 7523) where the JWT is exchanged at a token endpoint for an
747
+ * access token — that flow goes through `auth/oauth2/grants.ts` and
748
+ * uses this signer to mint the assertion.
749
+ *
750
+ * Algorithms:
751
+ * - HS256 / HS384 / HS512: HMAC with shared secret (most common).
752
+ * - RS256 / RS384 / RS512: RSA-SHA — caller supplies a private key
753
+ * in PKCS#8 PEM. Imported via `crypto.subtle.importKey`.
754
+ * - ES256 / ES384 / ES512: ECDSA-P256/384/521 — same PKCS#8 path.
755
+ * - **none**: refused. Always reject `alg: "none"` regardless of caller
756
+ * intent — RFC 8725 §3.1 calls this out as the canonical JWT mistake.
757
+ *
758
+ * The header `typ` defaults to `"JWT"`; callers can override via the
759
+ * `additionalHeaders` arg to set e.g. `kid` for key discovery.
760
+ */
761
+ type JwtAlgorithm = 'HS256' | 'HS384' | 'HS512' | 'RS256' | 'RS384' | 'RS512' | 'PS256' | 'PS384' | 'PS512' | 'ES256' | 'ES384' | 'ES512' | 'EdDSA';
762
+ interface JwtSignArgs {
763
+ /** Signing algorithm — must match the key material in `secretOrKey`. */
764
+ algorithm: JwtAlgorithm;
765
+ /**
766
+ * Shared secret for HMAC (HS256/384/512) or PEM-encoded PKCS#8
767
+ * private key for RSA / ECDSA. For HMAC, plain UTF-8 strings work;
768
+ * for asymmetric, the PEM must include the BEGIN/END markers.
769
+ */
770
+ secretOrKey: string;
771
+ /** Token claims. `iat` is auto-added when missing; `exp` is left to the caller. */
772
+ payload: Record<string, unknown>;
773
+ /** Extra header fields beyond `alg` and `typ` (e.g. `kid`, `cty`). */
774
+ additionalHeaders?: Record<string, unknown>;
775
+ }
776
+ declare function signJwt(args: JwtSignArgs): Promise<string>;
777
+
778
+ /**
779
+ * PKCE (Proof Key for Code Exchange — RFC 7636) primitives.
780
+ *
781
+ * Authorization-code grant adds a `code_verifier` (43–128 random URL-safe
782
+ * chars) generated on the client. The auth request carries a derived
783
+ * `code_challenge` (either the verifier itself for `plain`, or
784
+ * `BASE64URL(SHA-256(verifier))` for `S256`); the token-exchange request
785
+ * carries the verifier itself. The server confirms `H(verifier) === challenge`
786
+ * before issuing tokens, neutralizing intercepted authorization codes.
787
+ *
788
+ * S256 is the only method we recommend; `plain` exists in the spec for
789
+ * environments without SHA-256 (basically none today). We support both
790
+ * because some legacy IdPs still negotiate `plain` even when S256 is
791
+ * available.
792
+ */
793
+ /**
794
+ * Generate a fresh PKCE code verifier — 43..128 random characters from
795
+ * the unreserved URL set (RFC 3986 §2.3). Default length is 64, well
796
+ * inside the spec's allowed range.
797
+ *
798
+ * Sampling is uniform: we mask each random byte with `& 0x3f` to project
799
+ * onto exactly 64 alphabet positions, so every alphabet character is
800
+ * equally likely. The earlier `% 66` implementation introduced a small
801
+ * but measurable modulo bias.
802
+ */
803
+ declare function generateCodeVerifier(length?: number): string;
804
+ type PkceMethod = 'S256' | 'plain';
805
+ /**
806
+ * Compute the `code_challenge` that pairs with a verifier. The browser's
807
+ * SubtleCrypto handles SHA-256; for `plain` we just echo the verifier.
808
+ */
809
+ declare function computeCodeChallenge(verifier: string, method?: PkceMethod): Promise<string>;
810
+
811
+ /**
812
+ * The single token-endpoint client used by every OAuth2 grant.
813
+ *
814
+ * RFC 6749 §4 endpoints accept `application/x-www-form-urlencoded` with
815
+ * `grant_type` plus grant-specific fields. Client credentials may go in
816
+ * the Authorization header (`client_secret_basic`) or the body
817
+ * (`client_secret_post`); some IdPs only accept one or the other, so the
818
+ * caller picks via `clientAuthMethod`.
819
+ *
820
+ * On success the IdP returns:
821
+ *
822
+ * { access_token, token_type, expires_in?, refresh_token?, scope? }
823
+ *
824
+ * On failure (RFC 6749 §5.2):
825
+ *
826
+ * { error, error_description?, error_uri? } with HTTP 400/401
827
+ *
828
+ * We surface the failure as an Error whose `.message` includes both
829
+ * `error` and `error_description` so the UI can show "invalid_grant: bad
830
+ * code" without parsing JSON itself. The structured response is also
831
+ * attached to `.cause` for callers who need to dispatch on `error_code`
832
+ * (e.g. device flow's `authorization_pending` / `slow_down` cases).
833
+ */
834
+ interface OAuth2TokenResponse {
835
+ accessToken: string;
836
+ tokenType: string;
837
+ expiresIn?: number;
838
+ refreshToken?: string;
839
+ scope?: string;
840
+ /** Captured raw response — useful for OpenID Connect `id_token` etc. */
841
+ raw: Record<string, unknown>;
842
+ }
843
+ interface OAuth2ErrorResponse {
844
+ error: string;
845
+ errorDescription?: string;
846
+ errorUri?: string;
847
+ /** HTTP status the IdP returned (400, 401, 403, …). */
848
+ status: number;
849
+ /** Captured raw body — useful for diagnostics and grant-specific dispatch. */
850
+ raw: Record<string, unknown>;
851
+ }
852
+ /**
853
+ * Pre-signed client-assertion JWT for `private_key_jwt` client
854
+ * authentication (RFC 7521 §4.2 / RFC 7523 §2.2). The auth tab signs
855
+ * the assertion locally via `signJwt` and passes it here; we add the
856
+ * required `client_assertion` + `client_assertion_type` body fields.
857
+ * Used in place of `clientSecret` — the IdP verifies the assertion
858
+ * against the registered public key.
859
+ */
860
+ interface ClientAssertion {
861
+ /** The signed JWT (header.payload.signature) — output of `signJwt`. */
862
+ jwt: string;
863
+ /** RFC 7523 mandates this exact value. Other types exist (e.g. SAML2) but aren't used here. */
864
+ type?: string;
865
+ }
866
+ interface FetchOAuth2TokenArgs {
867
+ tokenUrl: string;
868
+ /**
869
+ * Grant-specific body fields. `grant_type` MUST be set by the caller —
870
+ * this helper doesn't infer it. Value type is `URLSearchParams` so the
871
+ * caller controls the exact wire format and can reuse the same body
872
+ * for retry / refresh paths.
873
+ */
874
+ body: URLSearchParams;
875
+ clientId: string;
876
+ /**
877
+ * Confidential clients only. Public clients (PKCE in a SPA) leave this
878
+ * undefined — the IdP recognizes the client by id alone.
879
+ */
880
+ clientSecret?: string;
881
+ /**
882
+ * Where to put the client credentials. `header` = HTTP Basic with
883
+ * `id:secret`. `body` = `client_id` + `client_secret` URL-form fields.
884
+ * Defaults to `header` (the RFC's preferred method).
885
+ *
886
+ * Ignored when `clientAssertion` is set — assertion takes precedence
887
+ * because mixing both is a misconfiguration the server will reject.
888
+ */
889
+ clientAuthMethod?: 'header' | 'body';
890
+ /**
891
+ * Optional `private_key_jwt` style client authentication. When set,
892
+ * the body carries `client_assertion` + `client_assertion_type` and
893
+ * `clientSecret` is NOT sent. Used by Azure AD, GCP, and any IdP
894
+ * that prefers signed assertions over shared secrets.
895
+ */
896
+ clientAssertion?: ClientAssertion;
897
+ /** Optional extra parameters appended to the body — IdP-specific knobs. */
898
+ extraParams?: Record<string, string>;
899
+ /** Override fetch for tests / desktop bridge. */
900
+ fetchImpl?: typeof fetch;
901
+ /** Per-call abort. Composed with the global request signal upstream. */
902
+ signal?: AbortSignal;
903
+ }
904
+ /**
905
+ * OAuth2 token endpoint failure. The structured `errorBody` carries the
906
+ * error code (`invalid_grant`, `authorization_pending`, etc) plus
907
+ * `error_description` / `error_uri` and the HTTP status — callers can
908
+ * dispatch on `errorBody.error` for grant-specific behavior (device
909
+ * flow's `authorization_pending` / `slow_down`, refresh re-auth, etc).
910
+ *
911
+ * Naming note: we deliberately don't shadow the standard `Error.cause`
912
+ * field. Callers using TypeScript can still use `instanceof` + the
913
+ * `errorBody` accessor; callers reading the `cause` property get the
914
+ * standard "what was the original exception" semantics.
915
+ */
916
+ declare class OAuth2TokenError extends Error {
917
+ readonly errorBody: OAuth2ErrorResponse;
918
+ constructor(errorBody: OAuth2ErrorResponse);
919
+ }
920
+ declare function fetchOAuth2Token(args: FetchOAuth2TokenArgs): Promise<OAuth2TokenResponse>;
921
+
922
+ /**
923
+ * Per-grant OAuth2 runners. Each function POSTs to the token endpoint
924
+ * with the body shape RFC 6749 specifies for that grant, and returns a
925
+ * normalized `OAuth2TokenResponse`. Callbacks (browser redirects to
926
+ * `redirect_uri?code=...`) and device-code polling intervals are the
927
+ * caller's job — these runners ONLY exchange.
928
+ *
929
+ * Refresh handling is in `refreshToken()` rather than per-grant: the
930
+ * refresh-token grant is identical regardless of which grant minted the
931
+ * original token.
932
+ */
933
+
934
+ type ClientAuthMethod = 'header' | 'body';
935
+ type FetchImpl = typeof fetch;
936
+ interface ClientCredentialsArgs {
937
+ tokenUrl: string;
938
+ clientId: string;
939
+ clientSecret: string;
940
+ scope?: string;
941
+ clientAuthMethod?: ClientAuthMethod;
942
+ extraParams?: Record<string, string>;
943
+ fetchImpl?: FetchImpl;
944
+ signal?: AbortSignal;
945
+ }
946
+ /** RFC 6749 §4.4 — machine-to-machine, no user. */
947
+ declare function runClientCredentials(args: ClientCredentialsArgs): Promise<OAuth2TokenResponse>;
948
+ interface RopcArgs {
949
+ tokenUrl: string;
950
+ clientId: string;
951
+ clientSecret?: string;
952
+ username: string;
953
+ password: string;
954
+ scope?: string;
955
+ clientAuthMethod?: ClientAuthMethod;
956
+ extraParams?: Record<string, string>;
957
+ fetchImpl?: FetchImpl;
958
+ signal?: AbortSignal;
959
+ }
960
+ /**
961
+ * RFC 6749 §4.3 — Resource Owner Password Credentials. Marked DEPRECATED
962
+ * in OAuth 2.1; we support it because legacy IdPs still require it for
963
+ * specific testing / migration scenarios. The auth panel surfaces a
964
+ * warning banner about the deprecation.
965
+ */
966
+ declare function runRopc(args: RopcArgs): Promise<OAuth2TokenResponse>;
967
+ interface AuthCodeExchangeArgs {
968
+ tokenUrl: string;
969
+ clientId: string;
970
+ clientSecret?: string;
971
+ /** The `code` value the IdP redirected back with. */
972
+ code: string;
973
+ /** Must match the redirect_uri sent in the initial /authorize request. */
974
+ redirectUri: string;
975
+ clientAuthMethod?: ClientAuthMethod;
976
+ extraParams?: Record<string, string>;
977
+ fetchImpl?: FetchImpl;
978
+ signal?: AbortSignal;
979
+ }
980
+ /** RFC 6749 §4.1 — Authorization Code grant. */
981
+ declare function exchangeAuthCode(args: AuthCodeExchangeArgs): Promise<OAuth2TokenResponse>;
982
+ interface PkceExchangeArgs extends AuthCodeExchangeArgs {
983
+ /** The verifier we generated when constructing the auth URL. */
984
+ codeVerifier: string;
985
+ }
986
+ /**
987
+ * RFC 7636 — Authorization Code with PKCE. Same body as plain auth-code
988
+ * plus `code_verifier`. clientSecret is optional (public clients omit it).
989
+ */
990
+ declare function exchangePkce(args: PkceExchangeArgs): Promise<OAuth2TokenResponse>;
991
+ interface DeviceAuthorizationResponse {
992
+ deviceCode: string;
993
+ userCode: string;
994
+ verificationUri: string;
995
+ /** Optional convenience URI with the user_code already embedded. */
996
+ verificationUriComplete?: string;
997
+ /** Seconds the device should poll the token endpoint. */
998
+ interval: number;
999
+ /** Seconds before deviceCode expires. */
1000
+ expiresIn: number;
1001
+ raw: Record<string, unknown>;
1002
+ }
1003
+ interface DeviceAuthorizationArgs {
1004
+ deviceAuthorizationUrl: string;
1005
+ clientId: string;
1006
+ clientSecret?: string;
1007
+ scope?: string;
1008
+ fetchImpl?: FetchImpl;
1009
+ signal?: AbortSignal;
1010
+ }
1011
+ /**
1012
+ * RFC 8628 §3.1 — Device Authorization Request. Returns the device_code
1013
+ * + user_code so the caller can show the user_code + verification URI to
1014
+ * a human, then poll the token endpoint with `pollDeviceFlow`.
1015
+ */
1016
+ declare function requestDeviceAuthorization(args: DeviceAuthorizationArgs): Promise<DeviceAuthorizationResponse>;
1017
+ interface PollDeviceFlowArgs {
1018
+ tokenUrl: string;
1019
+ clientId: string;
1020
+ clientSecret?: string;
1021
+ deviceCode: string;
1022
+ /** Initial poll interval in seconds. Bumped to `interval + 5` on `slow_down`. */
1023
+ intervalSeconds: number;
1024
+ /** Max overall wait. When elapsed, throws an `OAuth2TokenError` of error="expired_token". */
1025
+ maxWaitMs?: number;
1026
+ fetchImpl?: FetchImpl;
1027
+ signal?: AbortSignal;
1028
+ /**
1029
+ * Optional progress callback fired on every poll cycle. UI uses this
1030
+ * to update the visible "still waiting…" indicator and tick down the
1031
+ * remaining time. Receives `{ pollCount, elapsedMs, lastError }` —
1032
+ * `lastError` is set when the IdP responded `slow_down` so the UI
1033
+ * can hint the user to wait.
1034
+ */
1035
+ onPoll?: (info: {
1036
+ pollCount: number;
1037
+ elapsedMs: number;
1038
+ lastError?: string;
1039
+ }) => void;
1040
+ /** Test seam — overrides `setTimeout` / `Date.now` for fake clocks. */
1041
+ scheduler?: {
1042
+ sleep: (ms: number) => Promise<void>;
1043
+ now: () => number;
1044
+ };
1045
+ }
1046
+ /**
1047
+ * RFC 8628 §3.4 — poll the token endpoint until the user authorizes the
1048
+ * device, then return the token. Honors `authorization_pending` /
1049
+ * `slow_down` / `access_denied` / `expired_token`.
1050
+ */
1051
+ declare function pollDeviceFlow(args: PollDeviceFlowArgs): Promise<OAuth2TokenResponse>;
1052
+ interface RefreshTokenArgs {
1053
+ tokenUrl: string;
1054
+ clientId: string;
1055
+ clientSecret?: string;
1056
+ refreshToken: string;
1057
+ /** Some IdPs require the original scope on refresh. */
1058
+ scope?: string;
1059
+ clientAuthMethod?: ClientAuthMethod;
1060
+ extraParams?: Record<string, string>;
1061
+ fetchImpl?: FetchImpl;
1062
+ signal?: AbortSignal;
1063
+ }
1064
+ /**
1065
+ * RFC 6749 §6 — refresh a previously obtained token. Identical wire
1066
+ * shape regardless of the grant that minted the original token; the
1067
+ * caller decides when to call it (typical heuristic: when
1068
+ * `expiresAt < now + 60s`).
1069
+ */
1070
+ declare function refreshToken(args: RefreshTokenArgs): Promise<OAuth2TokenResponse>;
1071
+ /**
1072
+ * Build the `/authorize` URL the user is redirected to for auth-code or
1073
+ * implicit flows. PKCE callers append `code_challenge` + `code_challenge_method`
1074
+ * via `extraParams`. Doesn't open a browser — that's the host bridge's job.
1075
+ */
1076
+ declare function buildAuthorizeUrl(args: {
1077
+ authorizeUrl: string;
1078
+ clientId: string;
1079
+ redirectUri: string;
1080
+ responseType: 'code' | 'token';
1081
+ scope?: string;
1082
+ state?: string;
1083
+ extraParams?: Record<string, string>;
1084
+ }): string;
1085
+
1086
+ interface ResolveInheritedAuthArgs {
1087
+ /** The request's stated auth (may be `{ type: 'inherit' }`). */
1088
+ requestAuth: RequestAuth;
1089
+ /** The folderId the request lives in (null if at the root). */
1090
+ folderId: string | null;
1091
+ /** All known folders, keyed by id. */
1092
+ folders: Record<string, Folder>;
1093
+ }
1094
+ /**
1095
+ * If `requestAuth` is anything other than `inherit`, returns it unchanged.
1096
+ * Otherwise walks up the folder chain looking for the first folder whose
1097
+ * own `auth` is set and is not itself `inherit` or `none`. Folders with
1098
+ * `inherit` or `none` auth are transparent (skipped, walk continues).
1099
+ */
1100
+ declare function resolveInheritedAuth({ requestAuth, folderId, folders, }: ResolveInheritedAuthArgs): RequestAuth;
1101
+
1102
+ interface ParsedCurl {
1103
+ method: Request['method'];
1104
+ url: string;
1105
+ headers: Request['headers'];
1106
+ query: Request['query'];
1107
+ body: RequestBody;
1108
+ auth: RequestAuth;
1109
+ /** Unrecognised flags / fragments — the UI can show these as a warning. */
1110
+ warnings: string[];
1111
+ }
1112
+ /**
1113
+ * Tokenise a shell-style argv from a string. Handles single/double quotes,
1114
+ * backslash escapes, and whitespace-splitting. Doesn't try to be a full
1115
+ * POSIX shell — `$VAR` expansion, command substitution, and globs all
1116
+ * pass through verbatim.
1117
+ */
1118
+ declare function tokenizeCurl(input: string): string[];
1119
+ declare function parseCurl(input: string): ParsedCurl;
1120
+
1121
+ interface ImportedRequest {
1122
+ name: string;
1123
+ method: HttpMethod;
1124
+ url: string;
1125
+ headers: Array<{
1126
+ key: string;
1127
+ value: string;
1128
+ enabled: boolean;
1129
+ }>;
1130
+ query: Array<{
1131
+ key: string;
1132
+ value: string;
1133
+ enabled: boolean;
1134
+ }>;
1135
+ body: RequestBody;
1136
+ auth: RequestAuth;
1137
+ }
1138
+ interface ImportedFolder {
1139
+ name: string;
1140
+ /** Index path from root (deterministic id assignment is the caller's job). */
1141
+ pathIds: number[];
1142
+ parentPathIds: number[] | null;
1143
+ }
1144
+ interface ParsedPostmanCollection {
1145
+ collectionName: string;
1146
+ folders: ImportedFolder[];
1147
+ requests: Array<ImportedRequest & {
1148
+ folderPathIds: number[] | null;
1149
+ }>;
1150
+ warnings: string[];
1151
+ }
1152
+ declare function isPostmanV2Collection(doc: unknown): boolean;
1153
+ declare function parsePostmanCollection(input: string): ParsedPostmanCollection;
1154
+
1155
+ interface ParsedPostmanEnvironment {
1156
+ /** Suggested env name; the user can change at import time. */
1157
+ name: string;
1158
+ variables: EnvironmentVariable[];
1159
+ warnings: string[];
1160
+ }
1161
+ declare function isPostmanEnvironment(doc: unknown): boolean;
1162
+ declare function parsePostmanEnvironment(input: string): ParsedPostmanEnvironment;
1163
+
1164
+ declare function isInsomniaExport(doc: unknown): boolean;
1165
+ declare function parseInsomniaCollection(input: string): ParsedPostmanCollection;
1166
+
1167
+ /**
1168
+ * Result of evaluating one assertion against a response. Carries a snapshot
1169
+ * of the assertion definition so downstream UI (History detail view, run
1170
+ * exports, plan reports) can render the verdict without joining back to the
1171
+ * source request — which may have been renamed, edited, or deleted by the
1172
+ * time the user looks at history.
1173
+ */
1174
+ interface AssertionResult {
1175
+ assertionId: string;
1176
+ kind: Assertion['kind'];
1177
+ op: Assertion['op'];
1178
+ target?: string;
1179
+ expected: string | number;
1180
+ passed: boolean;
1181
+ /**
1182
+ * Human-readable explanation. Always populated by `runAssertions` — pass
1183
+ * cases get positive descriptions ("status: 200 equals 200"), fail cases
1184
+ * get the diff. Optional in the type because the persisted shape in
1185
+ * `RequestRun.assertions` predates this and may carry undefined for older
1186
+ * history entries.
1187
+ */
1188
+ detail?: string;
1189
+ }
1190
+ declare function runAssertions(assertions: ReadonlyArray<Assertion>, exec: ExecutionResult): AssertionResult[];
1191
+ /**
1192
+ * Read a dotted-path value from a JSON tree. Supports `a.b.c` and bracket
1193
+ * indexing `arr[0]`. Returns `undefined` for missing segments.
1194
+ */
1195
+ declare function readJsonPath(root: unknown, path: string): unknown;
1196
+
1197
+ interface ContextExtractionResult {
1198
+ extracted: Record<string, string>;
1199
+ warnings: string[];
1200
+ }
1201
+ declare function extractContext(result: ExecutionResult, extractions: ReadonlyArray<ContextExtraction>): ContextExtractionResult;
1202
+
1203
+ interface EncryptedPayload {
1204
+ iv: string;
1205
+ ciphertext: string;
1206
+ }
1207
+ /**
1208
+ * Encrypt a UTF-8 string with the given AES-GCM key. Returns base64-encoded
1209
+ * iv + ciphertext, safe to embed in JSON / push to Git.
1210
+ */
1211
+ declare function encryptString(plaintext: string, key: CryptoKey): Promise<EncryptedPayload>;
1212
+ /**
1213
+ * Decrypt a payload produced by `encryptString`. Throws on bad key, tampered
1214
+ * ciphertext, or malformed input.
1215
+ */
1216
+ declare function decryptString(payload: EncryptedPayload, key: CryptoKey): Promise<string>;
1217
+ /**
1218
+ * Generate a fresh AES-GCM 256-bit key. The host persists it (typically as
1219
+ * a JWK in IndexedDB) so subsequent sessions can decrypt prior values.
1220
+ */
1221
+ declare function generateAesKey(): Promise<CryptoKey>;
1222
+ /**
1223
+ * Generate a fresh per-slot salt (16 random bytes, base64-encoded). Salts are
1224
+ * stored in `synced.secretKeys[id].salt` — they're not secret, but they do
1225
+ * need to be stable for the slot's lifetime so ciphertext encrypted on one
1226
+ * device is decryptable on another.
1227
+ */
1228
+ declare function generateSlotSalt(): string;
1229
+ /**
1230
+ * Derive an AES-GCM key from a slot's plaintext value via PBKDF2-SHA-256.
1231
+ * The salt is base64 and travels through Git in `synced.secretKeys[id].salt`;
1232
+ * the value is user-supplied and never leaves the device. Same `(value,
1233
+ * salt)` pair always derives the same key, so a teammate cloning the repo
1234
+ * gets matching keys once they enter the slot value on their machine.
1235
+ */
1236
+ declare function deriveKeyFromSlotValue(value: string, saltBase64: string): Promise<CryptoKey>;
1237
+ /** Export an AES-GCM key as a JSON Web Key (for IDB storage). */
1238
+ declare function exportKey(key: CryptoKey): Promise<JsonWebKey>;
1239
+ /** Import an AES-GCM key previously exported via `exportKey`. */
1240
+ declare function importKey(jwk: JsonWebKey): Promise<CryptoKey>;
1241
+ /**
1242
+ * Serialize an EncryptedPayload to a single string we can store in
1243
+ * `Environment.variables[i].value`. The schema is `enc:v1:<iv>:<ciphertext>`
1244
+ * — versioned so we can rotate algorithms later without ambiguity.
1245
+ */
1246
+ declare function serializePayload(payload: EncryptedPayload): string;
1247
+ declare function tryParsePayload(value: string): EncryptedPayload | null;
1248
+
1249
+ /**
1250
+ * Validate a branch name against GitHub's ref rules. Returns null when the
1251
+ * name is acceptable, otherwise a short reason. We enforce a stricter
1252
+ * subset (no spaces, ASCII only, length ≤ 100) so the auto-generated names
1253
+ * always pass.
1254
+ */
1255
+ declare function validateBranchName(name: string): string | null;
1256
+ /** Lowercase ASCII slug, hyphenated, no leading/trailing/double hyphens. */
1257
+ declare function slugify(input: string): string;
1258
+ interface BranchNameOptions {
1259
+ /** The workspace's local display name (from the registry entry). */
1260
+ displayName: string;
1261
+ /** Inject a fixed id in tests; defaults to 6 random hex chars. */
1262
+ idGen?: () => string;
1263
+ }
1264
+ declare function generateWorkingBranchName(opts: BranchNameOptions): string;
1265
+
1266
+ /**
1267
+ * Stringify a WorkspaceSynced doc with deeply-sorted object keys + 2-space
1268
+ * indent + trailing newline (so editors don't re-stamp the file when the
1269
+ * user opens it). Arrays preserve their existing order — that's part of
1270
+ * the workspace's user-visible shape (priority list, tree children, etc).
1271
+ */
1272
+ declare function serializeWorkspaceForGit(synced: WorkspaceSynced): string;
1273
+
1274
+ /** Error thrown when the input fails any of our checks. `code` lets the UI
1275
+ * branch on the specific failure (oversized, bad JSON, wrong shape, etc.)
1276
+ * without parsing the message string. */
1277
+ declare class RemoteWorkspaceParseError extends Error {
1278
+ readonly code: 'oversized' | 'invalid-json' | 'not-object' | 'missing-workspace-id' | 'missing-collections' | 'missing-environments';
1279
+ constructor(message: string, code: 'oversized' | 'invalid-json' | 'not-object' | 'missing-workspace-id' | 'missing-collections' | 'missing-environments');
1280
+ }
1281
+ /**
1282
+ * Parse a remote `workspace.json` and return a `WorkspaceSynced` we can
1283
+ * safely merge into store state. Throws `RemoteWorkspaceParseError` on
1284
+ * any failure — callers should catch and surface to the user as a
1285
+ * "this workspace was modified by an incompatible version" message.
1286
+ *
1287
+ * The returned object is NOT a deep clone of the input; if any nested
1288
+ * object had a `__proto__` etc. key, that key was dropped at the reviver
1289
+ * level. Strings, numbers, and arrays pass through unchanged.
1290
+ *
1291
+ * The function is intentionally PERMISSIVE about unknown fields — newer
1292
+ * versions of Studio may add fields we don't know about, and we want
1293
+ * those workspaces to remain readable. We only enforce the fields the
1294
+ * existing codebase positively requires.
1295
+ */
1296
+ declare function parseWorkspaceJson(content: string): WorkspaceSynced;
1297
+
1298
+ /**
1299
+ * Return a copy of `synced` with every credential-bearing field in every
1300
+ * Request.auth blanked to ''. Identity fields are preserved. Pure — does
1301
+ * not mutate the input. Safe to call on partially-shaped workspaces.
1302
+ */
1303
+ declare function redactForGit(synced: WorkspaceSynced): WorkspaceSynced;
1304
+ /**
1305
+ * Scan the already-serialised workspace JSON for any credential-only
1306
+ * field name with a non-empty value. Throws if found — the push path
1307
+ * should treat the throw as fatal (refuse to upload).
1308
+ *
1309
+ * The match is intentionally narrow: only the names in
1310
+ * `PLAINTEXT_CREDENTIAL_FIELD_NAMES`, only with a NON-EMPTY string value.
1311
+ * An empty-string credential (`"password":""`) is acceptable — that's
1312
+ * what `redactForGit` produces.
1313
+ *
1314
+ * Implementation note: we use a regex rather than walking the parsed
1315
+ * tree because (a) the input has already been serialised, and (b) the
1316
+ * regex catches every nesting level without us having to know the shape.
1317
+ * The risk of false positives is bounded because the field names are
1318
+ * specific (no `value` / `token` / `key` in the list).
1319
+ */
1320
+ declare function assertNoPlaintextCredentials(serialized: string): void;
1321
+
1322
+ interface AttachmentSlotRef {
1323
+ slotId: string;
1324
+ sha256?: string;
1325
+ filename?: string;
1326
+ mimeType?: string;
1327
+ size?: number;
1328
+ }
1329
+ /**
1330
+ * Walk every request in the synced doc and return one entry per unique
1331
+ * attachment slotId it references. Form-data file rows and the binary
1332
+ * body's attachment ref both contribute. Duplicates (same slotId
1333
+ * referenced twice — defensive only; slot ids are normally unique) are
1334
+ * collapsed; the first occurrence wins.
1335
+ */
1336
+ declare function collectAttachmentSlots(synced: WorkspaceSynced): AttachmentSlotRef[];
1337
+
1338
+ interface ParsedVersion {
1339
+ major: number;
1340
+ minor: number;
1341
+ patch: number;
1342
+ prerelease: string | null;
1343
+ build: string | null;
1344
+ }
1345
+ declare function parseSemver(version: string): ParsedVersion | null;
1346
+ declare function isValidSemver(version: string): boolean;
1347
+ /**
1348
+ * Compare two semver strings. Returns negative if `a < b`, positive if
1349
+ * `a > b`, 0 if equal. Build metadata is ignored (per semver spec). A
1350
+ * prerelease label sorts BEFORE its corresponding release (1.0.0-rc.1 <
1351
+ * 1.0.0). Within prereleases, dot-separated identifiers compare numeric
1352
+ * vs string per the spec.
1353
+ */
1354
+ declare function compareSemver(a: string, b: string): number;
1355
+ /** Sort an array of semver strings — newest first (descending). */
1356
+ declare function sortVersionsDesc(versions: readonly string[]): string[];
1357
+
1358
+ interface PublishReleaseArgs {
1359
+ version: string;
1360
+ notes: string;
1361
+ /** Optional bookkeeping — the git commit SHA the release points at. */
1362
+ sha?: string;
1363
+ /** Optional bookkeeping — git tag name (the source of truth is the ledger). */
1364
+ tagName?: string;
1365
+ publishedAt?: string;
1366
+ }
1367
+ /**
1368
+ * Append a new release to `synced.releases.self.versions` and bump
1369
+ * `currentVersion`. Pure — does not touch IDB or Git.
1370
+ *
1371
+ * Throws on invalid semver, duplicate version, or invalid notes shape.
1372
+ */
1373
+ declare function publishRelease(synced: WorkspaceSynced, args: PublishReleaseArgs): Promise<WorkspaceSynced>;
1374
+ /** Flip the `deprecated` flag on a version. Soft signal — version is still installable. */
1375
+ declare function deprecateRelease(synced: WorkspaceSynced, version: string): WorkspaceSynced;
1376
+ /**
1377
+ * Flip the `yanked` flag on a version. Hard signal — consumers should
1378
+ * be told this version is broken / unsafe and offered a different one.
1379
+ */
1380
+ declare function yankRelease(synced: WorkspaceSynced, version: string): WorkspaceSynced;
1381
+
1382
+ type MonacoLanguage = 'json' | 'xml' | 'html' | 'graphql' | 'javascript' | 'yaml' | 'plaintext';
1383
+ declare function normalizeContentType(contentType?: string): string;
1384
+ declare function getLanguageFromContentType(contentType?: string): MonacoLanguage;
1385
+ /**
1386
+ * Map a workspace BodyType to its Monaco language. Used by the editor
1387
+ * to set the right syntax highlighter even before Content-Type lands.
1388
+ */
1389
+ declare function getLanguageFromBodyType(bodyType: 'none' | 'json' | 'text' | 'urlencoded' | 'form-data' | 'binary' | 'xml' | 'graphql'): MonacoLanguage;
1390
+ declare const supportedContentTypeLanguageMap: Readonly<Record<string, MonacoLanguage>>;
1391
+
1392
+ interface GraphQLSchemaInfo {
1393
+ /** Object/Interface types and their fields. */
1394
+ types: Map<string, {
1395
+ fields: GraphQLField[];
1396
+ }>;
1397
+ /** Top-level operations (Query, Mutation, Subscription). */
1398
+ rootTypes: {
1399
+ query?: string;
1400
+ mutation?: string;
1401
+ subscription?: string;
1402
+ };
1403
+ /** Scalar + enum names. */
1404
+ scalars: string[];
1405
+ enums: string[];
1406
+ }
1407
+ interface GraphQLField {
1408
+ name: string;
1409
+ type: string;
1410
+ description?: string;
1411
+ }
1412
+ declare function parseGraphqlSchema(source: string, kind: 'sdl' | 'introspection'): GraphQLSchemaInfo;
1413
+
1414
+ type DiffStatus = 'unchanged' | 'local-only' | 'remote-only' | 'both-equal' | 'conflict';
1415
+ type EntityBucket = 'request' | 'folder' | 'environment' | 'linkedWorkspace' | 'mockServer' | 'executionPlan' | 'secretKey' | 'globalSchema' | 'globalGraphql' | 'linkedRequestOverride' | 'linkedEnvOverride' | 'releasePerLink' | 'tree' | 'environmentsActive' | 'environmentsPriority' | 'releaseSelf' | 'secretCrypto';
1416
+ interface DiffEntry {
1417
+ bucket: EntityBucket;
1418
+ /** Entity id within the bucket. Empty string for singleton buckets. */
1419
+ key: string;
1420
+ status: DiffStatus;
1421
+ /** Human-readable label for the resolver UI. */
1422
+ label: string;
1423
+ base: unknown;
1424
+ local: unknown;
1425
+ remote: unknown;
1426
+ }
1427
+ interface ThreeWayDiff {
1428
+ entries: DiffEntry[];
1429
+ conflicts: DiffEntry[];
1430
+ }
1431
+ type ConflictResolution = 'mine' | 'theirs';
1432
+ /** Map keyed by `bucket:key` (e.g. `request:r-1`, `releaseSelf:`). */
1433
+ type ResolutionMap = Record<string, ConflictResolution>;
1434
+ /**
1435
+ * Compute the per-entity diff. Returns every entity touched on at least
1436
+ * one side, plus a flat list of conflicts (subset of entries with status
1437
+ * 'conflict') for the resolver.
1438
+ *
1439
+ * `base` is the lastPulledSnapshot. When null (first refresh ever), every
1440
+ * remote entity that doesn't match local becomes a conflict — there's no
1441
+ * shared ancestor to pick a side automatically.
1442
+ */
1443
+ declare function computeThreeWayDiff(base: WorkspaceSynced | null, local: WorkspaceSynced, remote: WorkspaceSynced): ThreeWayDiff;
1444
+ /**
1445
+ * Apply a fully-resolved diff: take fast-forwards (remote-only) into
1446
+ * local, keep local-only changes verbatim, and resolve every conflict
1447
+ * via the supplied `resolutions` map (`bucket:key` → 'mine' | 'theirs').
1448
+ *
1449
+ * Throws when any conflict is missing a resolution — the caller is
1450
+ * expected to populate the modal first.
1451
+ */
1452
+ declare function applyMerge(local: WorkspaceSynced, remote: WorkspaceSynced, diff: ThreeWayDiff, resolutions: ResolutionMap): WorkspaceSynced;
1453
+
1454
+ interface UnpushedChange {
1455
+ bucket: EntityBucket;
1456
+ /** Entity id within the bucket; empty string for singletons (tree, etc.). */
1457
+ key: string;
1458
+ label: string;
1459
+ kind: 'added' | 'modified' | 'removed';
1460
+ base: unknown;
1461
+ local: unknown;
1462
+ }
1463
+ interface UnpushedSummary {
1464
+ added: number;
1465
+ modified: number;
1466
+ removed: number;
1467
+ total: number;
1468
+ /** Per-entry list, sorted by bucket then label so the preview list renders predictably. */
1469
+ changes: UnpushedChange[];
1470
+ computedAt: string;
1471
+ }
1472
+ declare function summarizeUnpushedChanges(base: WorkspaceSynced | null, current: WorkspaceSynced, options?: {
1473
+ now?: () => Date;
1474
+ }): UnpushedSummary;
1475
+ /**
1476
+ * Cheap "anything to push?" check for the BranchCard badge. Avoids
1477
+ * recomputing the full preview list when the caller only needs a
1478
+ * boolean. Identity short-circuits on referential equality of `current`
1479
+ * and `base` — common when the store hasn't mutated since pull.
1480
+ */
1481
+ declare function hasUnpushedChanges(base: WorkspaceSynced | null, current: WorkspaceSynced): boolean;
1482
+ /** Stable empty value for callers that want to default-render an empty summary. */
1483
+ declare const EMPTY_UNPUSHED_SUMMARY: UnpushedSummary;
1484
+
1485
+ type LinkedUpdateStatus = 'unchanged' | 'source-only' | 'local-only' | 'both-changed' | 'new-in-source' | 'removed-in-source';
1486
+ type LinkedUpdateBucket = 'request' | 'folder' | 'environment-var';
1487
+ interface LinkedUpdateEntry<TBase = unknown, TTarget = unknown, TOverride = unknown> {
1488
+ bucket: LinkedUpdateBucket;
1489
+ /** Identifier scoped to the bucket. For env-var, format `<envName>:<varKey>`. */
1490
+ key: string;
1491
+ label: string;
1492
+ status: LinkedUpdateStatus;
1493
+ base: TBase | null;
1494
+ target: TTarget | null;
1495
+ override: TOverride | null;
1496
+ }
1497
+ interface LinkedUpdatePreview {
1498
+ fromVersion: string | null;
1499
+ toVersion: string;
1500
+ entries: LinkedUpdateEntry[];
1501
+ /** Quick counts for the modal summary line. */
1502
+ summary: Record<LinkedUpdateStatus, number>;
1503
+ }
1504
+ interface PreviewArgs {
1505
+ fromVersion: string | null;
1506
+ toVersion: string;
1507
+ base: LinkedSnapshot | null;
1508
+ target: LinkedSnapshot;
1509
+ /** All request overrides keyed by `${linkedWorkspaceId}:${itemId}` — caller pre-filters to one link. */
1510
+ requestOverrides: RequestOverride[];
1511
+ /** All env-var overrides for this link. */
1512
+ envVarOverrides: EnvironmentVariableOverride[];
1513
+ }
1514
+ /**
1515
+ * Pure function — returns a structured preview of every change between
1516
+ * `base` and `target`, classified by status and annotated with the
1517
+ * consumer's overrides where applicable. Caller renders the modal and
1518
+ * collects resolutions for `both-changed` entries.
1519
+ */
1520
+ declare function previewLinkedUpdate(args: PreviewArgs): LinkedUpdatePreview;
1521
+ /**
1522
+ * Map status → 'mine' | 'theirs' for entries the user has resolved.
1523
+ * 'mine' = keep the override / orphan. 'theirs' = adopt source.
1524
+ *
1525
+ * `source-only`, `new-in-source`, and `local-only` don't need a
1526
+ * resolution (auto-applied), so this map is keyed only by entries that
1527
+ * are `both-changed` or `removed-in-source` (the latter optionally lets
1528
+ * the user keep their override as a consumer-only request — a rarer
1529
+ * choice).
1530
+ */
1531
+ type LinkedUpdateResolutionMap = Record<string, 'mine' | 'theirs'>;
1532
+ interface ApplyArgs {
1533
+ base: LinkedSnapshot | null;
1534
+ target: LinkedSnapshot;
1535
+ preview: LinkedUpdatePreview;
1536
+ resolutions: LinkedUpdateResolutionMap;
1537
+ /** All overrides for this link, BEFORE the apply. */
1538
+ requestOverrides: RequestOverride[];
1539
+ envVarOverrides: EnvironmentVariableOverride[];
1540
+ }
1541
+ interface ApplyResult {
1542
+ /** New canonical snapshot to cache (replaces base). */
1543
+ nextSnapshot: LinkedSnapshot;
1544
+ /** Override entries the consumer keeps after applying. */
1545
+ nextRequestOverrides: RequestOverride[];
1546
+ nextEnvVarOverrides: EnvironmentVariableOverride[];
1547
+ /** Per-entry record of what we did, surfaced to the toast / activity log. */
1548
+ log: Array<{
1549
+ entryKey: string;
1550
+ bucket: LinkedUpdateBucket;
1551
+ action: string;
1552
+ }>;
1553
+ }
1554
+ /**
1555
+ * Apply a fully-resolved preview. Pure — does not touch IDB or the store.
1556
+ *
1557
+ * Throws when any `both-changed` entry is missing a resolution.
1558
+ */
1559
+ declare function applyLinkedUpdate(args: ApplyArgs): ApplyResult;
1560
+
1561
+ interface ApplyMutationOptions {
1562
+ /** ISO timestamp to stamp into `updatedAt`. Defaults to the current time. */
1563
+ now?: string;
1564
+ }
1565
+ interface ApplyMutationResult {
1566
+ next: WorkspaceState;
1567
+ changedIds: string[];
1568
+ }
1569
+ declare function applyMutation(state: WorkspaceState, patch: WorkspacePatch, options?: ApplyMutationOptions): ApplyMutationResult;
1570
+
1571
+ /**
1572
+ * Best-known identity of whoever launched a plan run. Recorded for display
1573
+ * and handed to the `authorize` hook. `unknown` is the headless default when
1574
+ * no GitHub session or OS user can be determined.
1575
+ */
1576
+ interface RunActor {
1577
+ kind: 'github' | 'os' | 'unknown';
1578
+ /** GitHub login, OS username, or 'unknown'. */
1579
+ name: string;
1580
+ }
1581
+ declare const ANONYMOUS_ACTOR: RunActor;
1582
+ /**
1583
+ * Thrown by an `authorize` hook to deny a run. `runPlan` lets it propagate
1584
+ * untouched so callers (the CLI) can map it to a distinct exit code. Today no
1585
+ * built-in hook throws it — it exists for the per-user run restrictions that
1586
+ * are planned but not yet designed.
1587
+ */
1588
+ declare class PlanRunDeniedError extends Error {
1589
+ constructor(message: string);
1590
+ }
1591
+ /** Context handed to the `authorize` hook before any HTTP request fires. */
1592
+ interface PlanRunAuthorizationContext {
1593
+ planId: string;
1594
+ plan: ExecutionPlan;
1595
+ actor: RunActor;
1596
+ state: WorkspaceState;
1597
+ }
1598
+ interface RunPlanOptions {
1599
+ /** Evaluate the per-request assertions. Defaults to `true`. */
1600
+ withAssertions?: boolean;
1601
+ /**
1602
+ * Halt the run after the first failed step — including missing / linked
1603
+ * steps — regardless of the plan's own `stopOnAssertionFailure`. This is
1604
+ * the `apicircle run --bail` behaviour. Defaults to `false`.
1605
+ */
1606
+ bail?: boolean;
1607
+ /**
1608
+ * Name of a local environment to layer on top of the run's env priority
1609
+ * order (highest precedence). Used by `apicircle run --env <name>`. A name
1610
+ * with no matching environment simply contributes nothing.
1611
+ */
1612
+ env?: string;
1613
+ /** Injected fetch — defaults to `globalThis.fetch`. Tests pass a stub. */
1614
+ fetchImpl?: typeof fetch;
1615
+ /** Aborts the run between steps and the in-flight request. */
1616
+ signal?: AbortSignal;
1617
+ /** Per-request hard timeout in ms. `null` disables. Defaults to executeRequest's 30s. */
1618
+ timeoutMs?: number | null;
1619
+ /** Plaintext secret values keyed by `secretKeyId`, for encrypted env vars. */
1620
+ secretsById?: Record<string, string>;
1621
+ /** Identity of whoever launched the run. Defaults to {@link ANONYMOUS_ACTOR}. */
1622
+ actor?: RunActor;
1623
+ /**
1624
+ * Authorization seam. Called once, before the first request, with the
1625
+ * resolved plan + actor. Throw (ideally {@link PlanRunDeniedError}) to deny
1626
+ * the run. Omit for an unrestricted run — the current default everywhere.
1627
+ */
1628
+ authorize?: (ctx: PlanRunAuthorizationContext) => void | Promise<void>;
1629
+ /** Invoked after each step settles — lets a CLI stream progress live. */
1630
+ onStep?: (step: PlanStepResult) => void;
1631
+ }
1632
+ interface PlanStepResult {
1633
+ /** Index into `plan.steps` — stable even when steps are skipped. */
1634
+ stepIndex: number;
1635
+ requestId: string;
1636
+ requestName: string;
1637
+ requestMethod: string;
1638
+ /** True when the step was skipped via `enabled: false`. */
1639
+ skipped: boolean;
1640
+ /** Execution result, or `null` for a skipped / unresolvable step. */
1641
+ result: ExecutionResult | null;
1642
+ assertionResults: AssertionResult[];
1643
+ /** `{{VAR}}` placeholders that didn't resolve in url / headers / query / body / auth. */
1644
+ missingVariables: string[];
1645
+ /** True when the request succeeded and (if enabled) every assertion passed. */
1646
+ passed: boolean;
1647
+ /** Set when the step couldn't run at all (missing / linked / unsupported). */
1648
+ error?: string;
1649
+ }
1650
+ interface RunPlanResult {
1651
+ planRun: PlanRun;
1652
+ /** One entry per step, including skipped ones (in `plan.steps` order). */
1653
+ steps: PlanStepResult[];
1654
+ /**
1655
+ * Workspace with the plan-run + request-runs appended to history and any
1656
+ * refreshed OAuth2 tokens persisted onto `synced`. Save this back to disk.
1657
+ */
1658
+ nextState: WorkspaceState;
1659
+ /** True when every executed (non-skipped) step passed. Vacuously true when none ran. */
1660
+ passed: boolean;
1661
+ }
1662
+ type ResolvePlanRefResult = {
1663
+ ok: true;
1664
+ id: string;
1665
+ plan: ExecutionPlan;
1666
+ } | {
1667
+ ok: false;
1668
+ error: string;
1669
+ available: string[];
1670
+ };
1671
+ /**
1672
+ * Resolve a user-supplied plan reference (a plan id, or a plan name) against a
1673
+ * workspace. Name matching is case-insensitive and trimmed; an ambiguous name
1674
+ * (two plans share it) is rejected so the caller can ask for an id instead.
1675
+ */
1676
+ declare function resolvePlanRef(synced: WorkspaceSynced, ref: string): ResolvePlanRefResult;
1677
+ /**
1678
+ * Execute every enabled step of `planId` against the workspace. Never throws
1679
+ * for HTTP / assertion failures — those land in the returned step results.
1680
+ * Throws only for a missing plan or a denial from the `authorize` hook.
1681
+ */
1682
+ declare function runPlan(state: WorkspaceState, planId: string, opts?: RunPlanOptions): Promise<RunPlanResult>;
1683
+
1684
+ /**
1685
+ * Token Oriented Object Notation (TOON) encoder.
1686
+ *
1687
+ * Compact, indentation-based serialization that drops most of JSON's
1688
+ * structural noise (quotes around keys/values where unambiguous, braces,
1689
+ * commas, colons-with-spaces). Optimized for LLM token budgets and
1690
+ * eyeballing — typical JSON shrinks 25–50% when re-encoded.
1691
+ *
1692
+ * Two encoding shapes are produced:
1693
+ *
1694
+ * - **Tabular** for arrays of homogeneous flat objects (common API list
1695
+ * payloads). The header lists keys once, the rows list values once:
1696
+ *
1697
+ * users[2]{id,name,active}:
1698
+ * 1,Alice,true
1699
+ * 2,Bob,false
1700
+ *
1701
+ * - **Indented** for everything else:
1702
+ *
1703
+ * meta:
1704
+ * page: 1
1705
+ * items:
1706
+ * - id: 1
1707
+ * name: Alice
1708
+ *
1709
+ * Strings are quoted only when they contain characters that would otherwise
1710
+ * break the line shape (commas, colons, leading/trailing whitespace, etc.).
1711
+ * Output is intentionally lossless for round-tripping primitive types — but
1712
+ * a TOON decoder is out of scope for this module: the encoder exists so the
1713
+ * UI can show an "X% smaller" hint and an optional preview.
1714
+ */
1715
+ type Json$2 = string | number | boolean | null | Json$2[] | {
1716
+ [key: string]: Json$2;
1717
+ };
1718
+ declare function toToon(value: Json$2): string;
1719
+
1720
+ /**
1721
+ * Minimal block-style YAML encoder. Sibling of `toon.ts` — same job
1722
+ * (compact, indentation-based representation of JSON-shaped data) but
1723
+ * sticks to standard YAML syntax instead of TOON's tabular shorthand,
1724
+ * so the user can compare the two.
1725
+ *
1726
+ * For arrays of homogeneous flat objects YAML still emits one list item
1727
+ * per row (unlike TOON's `name[count]{cols}: rows` table), so YAML is
1728
+ * usually slightly larger than TOON on tabular payloads but identical or
1729
+ * very close on nested ones. Keeping both gives the user the full picture
1730
+ * when deciding which format to feed downstream.
1731
+ */
1732
+ type Json$1 = string | number | boolean | null | Json$1[] | {
1733
+ [key: string]: Json$1;
1734
+ };
1735
+ declare function toYaml(value: Json$1): string;
1736
+
1737
+ /**
1738
+ * Minimal RFC 4180-ish CSV encoder, used purely as a "what if you sent
1739
+ * this as CSV" savings preview. Returns null when the input isn't an
1740
+ * array of homogeneous flat objects — CSV only makes sense for tabular
1741
+ * data, and forcing it on nested JSON would either lose information or
1742
+ * inflate the payload, defeating the point of the savings hint.
1743
+ */
1744
+ type Json = string | number | boolean | null | Json[] | {
1745
+ [key: string]: Json;
1746
+ };
1747
+ declare function toCsv(value: Json): string | null;
1748
+
1749
+ /**
1750
+ * Output formats we measure savings for. Minification is NOT in this
1751
+ * list on purpose — stripping whitespace from pretty-printed JSON isn't
1752
+ * a "transformation", it's the wire-effective baseline most APIs already
1753
+ * send. Comparing TOON/YAML/CSV against pretty JSON would inflate the
1754
+ * apparent savings; we always normalize to minified JSON first.
1755
+ */
1756
+ type TransformFormat = 'toon' | 'yaml' | 'csv';
1757
+ interface TransformCandidate {
1758
+ format: TransformFormat;
1759
+ /** Encoded payload. Available so the UI can offer "view" / "copy". */
1760
+ preview: string;
1761
+ /** UTF-8 bytes of `preview`. */
1762
+ bytes: number;
1763
+ /**
1764
+ * Bytes saved vs `minifiedBytes` (the wire baseline), expressed as a
1765
+ * percentage with one decimal. Clamped at 0 — candidates that don't
1766
+ * beat the baseline are dropped from `candidates` entirely.
1767
+ */
1768
+ percentSaved: number;
1769
+ }
1770
+ interface TransformSavings {
1771
+ /** UTF-8 bytes of the body as received (may be pretty-printed). */
1772
+ originalBytes: number;
1773
+ /**
1774
+ * UTF-8 bytes of the same body re-emitted as compact JSON. This is
1775
+ * the honest wire-baseline — most APIs already send minified JSON,
1776
+ * and any "transformation savings" should be measured against that,
1777
+ * not against a verbose pretty-printed version. When `originalBytes ===
1778
+ * minifiedBytes`, the wire body was already compact; when they differ,
1779
+ * the UI can surface that delta separately as a "minify only" tip
1780
+ * without mixing it into transformation savings.
1781
+ */
1782
+ minifiedBytes: number;
1783
+ /** Sorted by percentSaved descending. Empty when nothing beats minified. */
1784
+ candidates: TransformCandidate[];
1785
+ }
1786
+ /**
1787
+ * Compute savings candidates for a response body. Only JSON-shaped
1788
+ * content is inspected — binary, plain text, and HTML return an empty
1789
+ * candidate list. Pure, no side effects.
1790
+ *
1791
+ * Baselines:
1792
+ * - `originalBytes` : received-as-is. What the editor is currently rendering.
1793
+ * - `minifiedBytes` : what the wire would have carried with whitespace stripped.
1794
+ * - `candidates[].percentSaved` : measured against `minifiedBytes`. So a
1795
+ * "20% smaller as TOON" claim means TOON beats compact JSON by 20%,
1796
+ * not that it beats pretty JSON by 20%.
1797
+ */
1798
+ declare function computeTransformSavings(body: string, contentType?: string): TransformSavings;
1799
+ declare const TRANSFORM_FORMAT_LABELS: Record<TransformFormat, string>;
1800
+
1801
+ export { ANONYMOUS_ACTOR, type ApplyMutationOptions, type ApplyMutationResult, type AssertionResult, type AttachmentResolver, type AttachmentSlotRef, type AuthApplyOptions, type AuthApplyResult, type AuthApplyTarget, type AuthApplyWarning, type AuthCodeExchangeArgs, type AutoHeaderOverrides, type BranchNameOptions, type BuildDigestArgs, type BuildNtlmType3Args, type BuildRequestOptions, type BuiltRequest, type ClientCredentialsArgs, type ConflictResolution, type HeaderEntry$1 as ContentTypeHeaderEntry, type ContextExtractionResult, DESKTOP_APP_ORIGIN, type DeviceAuthorizationArgs, type DeviceAuthorizationResponse, type DiffEntry, type DiffStatus, type DigestChallenge, EMPTY_UNPUSHED_SUMMARY, type EncryptedPayload, type EntityBucket, type ExecuteOptions, type ExecutionResult, type FetchOAuth2TokenArgs, type GraphQLField, type GraphQLSchemaInfo, HTTP_HEADERS_MAP, type HawkSignArgs, type HeaderEntry, type HeaderSuggestionMode, type ImportedFolder, type ImportedRequest, type JwtAlgorithm, type JwtSignArgs, type ApplyArgs as LinkedApplyArgs, type ApplyResult as LinkedApplyResult, type PreviewArgs as LinkedPreviewArgs, type LinkedUpdateBucket, type LinkedUpdateEntry, type LinkedUpdatePreview, type LinkedUpdateResolutionMap, type LinkedUpdateStatus, type MonacoLanguage, type NtlmType2Challenge, type OAuth2ErrorResponse, OAuth2TokenError, type OAuth2TokenResponse, type ParsedCurl, type ParsedPostmanCollection, type ParsedPostmanEnvironment, type ParsedVersion, type PkceExchangeArgs, type PkceMethod, type PlanRunAuthorizationContext, PlanRunDeniedError, type PlanStepResult, type PollDeviceFlowArgs, type PreSendBlocker, type PreSendValidationInput, type PreSendValidationResult, type PreSendWarning, type PublishReleaseArgs, type RefreshTokenArgs, RemoteWorkspaceParseError, type ResolutionMap, type ResolutionScope, type ResolveInheritedAuthArgs, type ResolvePlanRefResult, type ResolveResult, type RopcArgs, type RunActor, type RunPlanOptions, type RunPlanResult, type SigV4SignArgs, type SigV4SignResult, TRANSFORM_FORMAT_LABELS, type ThreeWayDiff, type TransformCandidate, type TransformFormat, type TransformSavings, type UnpushedChange, type UnpushedSummary, type VariableSource, type VariableSuggestion, WorkspacePatch, WorkspaceState, applyAuth, applyAwsSigV4, applyContentTypeForBodyType, applyLinkedUpdate, applyMerge, applyMutation, applyPathParams, assertNoPlaintextCredentials, buildAuthorizeUrl, buildAutoHeaders, buildDigestAuthHeader, buildHawkAuthHeader, buildNtlmType1Negotiate, buildNtlmType3Authenticate, buildRequest, buildScope, collectAttachmentSlots, collectVariableSuggestions, compareSemver, composeBody, composeCookieHeader, composeHeaders, composeUrl, composeUrlWithQuery, computeCodeChallenge, computeThreeWayDiff, computeTransformSavings, decryptString, deprecateRelease, deriveKeyFromSlotValue, encryptString, exchangeAuthCode, exchangePkce, executeRequest, exportKey, extractContext, fetchOAuth2Token, findPathPlaceholders, generateAesKey, generateCodeVerifier, generateSlotSalt, generateSpanId, generateTraceParent, generateWorkingBranchName, getBodyTypeForContentType, getContentTypeForBodyType, getHeaderEntry, getHeaderValues, getLanguageFromBodyType, getLanguageFromContentType, getVariableAutocomplete, hasUnpushedChanges, importKey, isDesktop, isInsomniaExport, isPostmanEnvironment, isPostmanV2Collection, isValidSemver, lookup, mergeWithAutoHeaders, normalizeContentType, parseCurl, parseDigestChallenge, parseGraphqlSchema, parseInsomniaCollection, parseNtlmType2Challenge, parsePostmanCollection, parsePostmanEnvironment, parseSemver, parseUrlQuery, parseWorkspaceJson, pollDeviceFlow, preSendValidation, previewLinkedUpdate, publishRelease, readJsonPath, redactForGit, refreshToken, requestDeviceAuthorization, requestRunToExecutionResult, resolveInheritedAuth, resolvePlanRef, resolveString, resolveStringMap, runAssertions, runClientCredentials, runPlan, runRopc, serializePayload, serializeWorkspaceForGit, signJwt, slugify, sortVersionsDesc, suggestHeaders, summarizeUnpushedChanges, supportedContentTypeLanguageMap, toCsv, toToon, toYaml, tokenizeCurl, tryParsePayload, validateBranchName, yankRelease };