/** * Implicit HTTP steps (P6.1) — shared identity, envelope, and replay logic. * * A bare `await fetch(...)` inside a workflow body is durable with zero * annotation: the undici dispatcher interceptor (see `http-interceptor.ts`) * consults the active workflow context (see `runtime-context.ts`) to decide * whether the call is a first encounter (execute + record) or a re-run * (reconstruct the recorded Response). * * This module is transport-agnostic: `WorkflowContext` (server-backed) and * `WorkflowContextLite` (embedded engine) each implement `ImplicitStepHost`, * and the interceptor drives them the same way (Design Law 3). * * Step identity (Design Laws 1, 9): the step_key is content-addressed on the * *call site* — the top workflow-code frame of a captured stack, normalized to * `relativePath:line` — NOT the URL. URLs legitimately embed checkpoint-derived * dynamic values (an id memoized from a prior step); they are stable on re-run * only because those values are memoized, so they are unfit for identity. The * call site is the stable identity. The recorded method+URL live in checkpoint * metadata: the key is hard-validated (NonDeterminismError on mismatch), URL * drift is a soft warning. */ /** Config knob: how implicit HTTP steps derive their step identity. */ export type ImplicitIdentityMode = 'callsite' | 'url'; /** * Per-worker/per-context implicit-step configuration. Defaults make bare * fetch durable; users opt out with `implicitSteps: false` (pass-through) or * switch identity source with `identity: 'url'`. */ export interface ImplicitStepsConfig { /** Master switch. Default true. When false, fetch is byte-identical pass-through. */ enabled: boolean; /** Step-identity source. Default 'callsite'. */ identity: ImplicitIdentityMode; /** * Statuses that throw into user code as an error (like an inline call would). * Network errors always throw. Default: throw on none (any status resolves, * matching `fetch` semantics); set e.g. `throwOnStatus: (s) => s >= 500` to * make 5xx throw. Configurable per the task's failure-semantics table. */ throwOnServerError: boolean; /** * Streaming/response body buffer cap in bytes. Bodies over this size buffer * up to the cap then route through the claim-check blob store server-side. * Default 5 MiB. */ maxBodyBytes: number; /** * Host/substring list identifying LLM provider endpoints. When an * intercepted URL matches, token/cost metadata is parsed into the P3.1 * `meta` column automatically. Defaults cover OpenAI/Anthropic/Google. */ llmEndpoints: string[]; /** * F6: when an intercepted response announces itself as a live stream * (`content-type: text/event-stream`), forward it to user code untouched * as a NON-durable passthrough (one warning per call site) instead of * buffering it to completion. Default true. Set false to buffer streams * to completion (destroys live-token latency; subject to maxBodyBytes). */ streamPassthrough: boolean; /** * F-POST: warn once per call site when a mutating method (non-GET/HEAD/ * OPTIONS) executes without an `Idempotency-Key` header — an at-least-once * effect without a key can double-fire across crash recovery. Default true. * Only fires when `autoIdempotencyKey` is disabled (with it on, every * mutating implicit call carries a key). */ warnOnUnkeyedMutations: boolean; /** * N2: inject a deterministic `Idempotency-Key` header on every mutating * (non-GET/HEAD/OPTIONS) implicit call that doesn't already carry one, * derived from `run_id + step_key` AT DISPATCH TIME — the interceptor * already holds this step's key, so the injected key is stable across * re-runs and retries by construction (unlike the deprecated no-arg * `ctx.idempotencyKey()`, whose "most recent HTTP step" default could * name a DIFFERENT step under concurrent branches). Default true. A * user-supplied Idempotency-Key header always wins. */ autoIdempotencyKey: boolean; } export declare const DEFAULT_MAX_BODY_BYTES: number; export declare const DEFAULT_LLM_ENDPOINTS: string[]; export declare function defaultImplicitStepsConfig(overrides?: Partial): ImplicitStepsConfig; /** Step-kind label used in HTTP step-key hashing (distinct from ACTIVITY etc.). */ export declare const HTTP_STEP_KIND = "HTTP"; /** * The recorded HTTP effect, serialized into a checkpoint. Reconstructs a * `Response` on re-run. Body bytes are base64 so the whole thing is a plain * JSON value (the engine/store treats it opaquely, same as a side-effect * value). */ export interface HttpEffectRecord { /** Marker so a replay path can distinguish this from other side-effect values. */ $http: 1; status: number; statusText: string; /** Selected response headers as [name, value] pairs (lowercased names). */ headers: [string, string][]; /** base64 of the response body bytes. */ bodyB64: string; method: string; url: string; durationMs: number; /** * F4: set when the recorded outcome was a FAILURE (network error — the * request never produced a response). Replay rethrows this error instead * of re-issuing the request, so error-dependent control flow (try/catch * around a bare fetch) is reproducible across runs. `status` is 0 and the * body fields are empty on an error record. */ error?: { name: string; message: string; }; /** * F13: sha256[:16] of the request body when it was a string/bytes payload * (streams/FormData are not hashed). Replay compares it to the current * call's body and soft-warns on drift — the request-body analog of the * URL-drift warning, catching e.g. a changed prompt replaying a stale * recorded completion. */ requestBodySha?: string; } export interface DecodedHttpEffectRecord { record: HttpEffectRecord; bodyBytes: Buffer; } /** Validate and decode every field consumed while replaying an HTTP checkpoint. */ export declare function decodeHttpEffectRecord(value: unknown, operation: string): DecodedHttpEffectRecord; /** Captured response pieces, produced by the interceptor before recording. */ export interface CapturedResponse { status: number; statusText: string; /** Raw header pairs (any casing). */ headers: [string, string][]; body: Uint8Array; method: string; url: string; durationMs: number; } export declare function buildHttpEffectRecord(cap: CapturedResponse): HttpEffectRecord; /** * Reconstruct a `Response` from a recorded effect. The body is served as a * ReadableStream (so `Response.body` streaming semantics are preserved on * re-run, per the payload-discipline requirement) and an * `x-memoflow-replayed: 1` header is added for debuggability. */ export declare function reconstructResponse(record: HttpEffectRecord): Response; /** * Compute the identity source for an HTTP call. * * - 'callsite' (default): the top workflow-code frame of the captured stack, * normalized to `relativePath:line`. Frames inside the SDK and node * internals are skipped so the identity is the user's `fetch(...)` line. * - 'url': method + URL (for users who prefer URL-shaped identity). */ export declare function identitySource(mode: ImplicitIdentityMode, method: string, url: string, stack: string | undefined): string; /** * Extract the top non-SDK, non-internal frame from a captured stack and * normalize it to `pkgName:path:line`. Returns undefined if no user frame * is found. * * F7 (P9.3): the path is made relative to the calling file's nearest * PACKAGE ROOT (the closest directory with a package.json, prefixed with * that package's name when it has one) and forward-slashed — so the * identity is machine-independent: two workers running the same package * from different install directories (or different OSes) compute the SAME * identity for the same fetch line. Falls back to a cwd-relative path when * no package.json exists anywhere above the file. */ export declare function callSiteFromStack(stack: string | undefined): string | undefined; /** * Parse token/cost metadata from an LLM provider response (P3.1 synergy). * Returns undefined when the URL isn't an LLM endpoint or the body isn't * parseable usage JSON. Best-effort — never throws into the workflow. */ export declare function parseLlmMeta(url: string, body: Uint8Array, llmEndpoints: string[]): Record | undefined; /** Deterministic sha256-based idempotency key derived from run_id + step_key. */ export declare function idempotencyKeyFor(runId: string, stepKey: string): string; /** * F5: thrown (never recorded) when a buffered implicit-step response exceeds * `maxBodyBytes`. Policy errors are not effect outcomes — raising the cap or * switching the call to `ctx.raw.fetch`/an explicit activity lets the call * re-execute on the next run. */ export declare class ImplicitStepPayloadTooLargeError extends Error { constructor(source: string, maxBodyBytes: number); } /** F4: build the durable record of a failed HTTP effect (network error). */ export declare function buildHttpErrorRecord(method: string, url: string, err: unknown, durationMs: number): HttpEffectRecord; /** * F13: hash a request body for drift detection. Only string/bytes payloads * are hashable — streams, FormData, and other exotic bodies return undefined * (no hash recorded, no drift check on replay). */ export declare function hashRequestBody(body: unknown): string | undefined; /** F13: soft-warn when a replayed record's request-body hash differs from the current call's. */ export declare function warnOnRequestBodyDrift(source: string, record: HttpEffectRecord, currentBodySha: string | undefined): void; /** F4: true when a recorded effect is a failure record. */ export declare function isHttpErrorRecord(record: HttpEffectRecord): boolean; /** * F4: reconstruct the recorded failure for replay — same name/message as the * original (stack and object identity necessarily differ; the replay is * flagged via `memoflowReplayed` for debuggability). */ export declare function reconstructHttpError(record: HttpEffectRecord): Error;