/** * QAR envelope discriminated union — public surface. * * Backed by the codegen output in `./generated/envelope.ts` (regenerated from * `schemas/qar/envelope.json` via `npm run codegen:qar`). This file is the * curated overlay: it re-exports the codegen variants under both their * QAR-native names (`TaskStart`, `TaskContinue`, …) and the SDK-2.0 * `…Envelope` aliases, and it sharpens the codegen output where the wire * is stricter than the schema can express: * * - `kind` is required (Pydantic exports it with a default, which makes * json-schema-to-typescript emit it optional). On the wire it is always * present and acts as the discriminator. * - `envelope_version` is the literal `1` (same Pydantic-default reason). * - `message_id` / `session_id` / `parent_message_id` are branded UUID * strings (`MessageId` / `SessionId`) so callers cannot accidentally * swap them at API boundaries — a property the JSON Schema can't * express but the SDK relies on. * * The direction whitelists below stay hand-written: they're an SDK-side * projection of QAR's `protocol/envelopes.py` taxonomy. */ import type { TaskStart, TaskContinue, TaskCancel, TaskResubscribe, TaskDelta, TaskDone, ToolRequest, ToolResponse, StateUpdate, AgentSpawn, ToolActivity, BulletinPost, ArtifactAdd, FlowPause, FlowResume, ErrorEnv } from './generated/envelope.js'; import type { MessageId, SessionId } from './ids.js'; import type { TaskCancelingPayload, TaskForceResumePayload, TaskForceResumedPayload, TaskStartedPayload, TaskStartPayload, ToolRequestPayload, ToolResponsePayload } from './payloads.js'; /** Envelope-version literal. QAR bumps this on any breaking schema change. */ export type EnvelopeVersion = 1; /** * Sharpens a codegen ONGOING envelope variant: marks `kind` + * `envelope_version` required, brands the UUID id fields. Generic so we * apply it once per variant without duplicating the field list. * * Corresponds to `_OngoingEnvelopeBase` — every kind EXCEPT * `task.start` carries `session_id` on the wire. The companion * {@link SharpenStart} is the `_StartEnvelopeBase` shape (no * `session_id`); the SDK's start encoder strips the field at emit-time, * but the public wire overlay must mirror the omission so raw senders * via {@link QodoClient.send} can't compile an invalid `task.start`. */ type Sharpen = Omit & { readonly kind: NonNullable; readonly envelope_version: EnvelopeVersion; readonly message_id: MessageId; readonly session_id: SessionId; readonly parent_message_id?: MessageId | null; }; /** * Sharpens a `_StartEnvelopeBase` codegen variant — same as * {@link Sharpen} but WITHOUT `session_id`. The `task.start` envelope * does not carry `session_id` on the wire; the server derives it from * `(tenant_id, payload.idempotency_key)` during the ingress * bind-and-derive phase. QAR's Pydantic model has `extra="forbid"`, so * any stray `session_id` on a `task.start` would fail server-side * parsing. */ type SharpenStart = Omit & { readonly kind: NonNullable; readonly envelope_version: EnvelopeVersion; readonly message_id: MessageId; readonly parent_message_id?: MessageId | null; }; /** * `task.start` envelope — `payload` is the discriminated union * (`{ agent_id, ... } | { agent, ... } | { graph, ... }`) rather than * the pre-amendment single-variant codegen shape. The generated * `TaskStart.payload` still carries the original * `{ agent_id, input, skill? }` shape; the `Omit<…, 'payload'> & { payload: * TaskStartPayload }` swap masks it until QAR's schema export catches * up. * * Built on {@link SharpenStart} (not {@link Sharpen}) so the type has * NO `session_id` field — `task.start` is QAR's `_StartEnvelopeBase`, * and the server-side `extra="forbid"` Pydantic config rejects a stray * `session_id`. The SDK's encoder (`Connection.sendEnvelope` for * `kind === 'task.start'`) strips the field at emit-time; the type * narrowing here ensures raw senders via {@link QodoClient.send} can't * compile an invalid envelope either. */ export type TaskStartEnvelope = Omit, 'payload'> & { readonly payload: TaskStartPayload; }; /** * `task.started` envelope — admission-commit ack. * * Hand-rolled `EnvelopeBase + { kind, payload }` shape because the * variant is not yet in the vendored schema bundle. Same pattern the * parallel-batched tool envelopes use while their schema bump is in * flight; when the schema is bumped and `npm run codegen:qar` * regenerates `./generated/envelope.ts`, fold this back into a * `Sharpen<>` over the codegen variant. * * Per the QAR wire `_OngoingEnvelopeBase.parent_message_id` narrowing * for this kind: the field is **required and non-nullable** — it equals * the originating `task.start.message_id` and the SDK uses it for * correlation. The SDK's `parseEnvelope` does NOT enforce the * narrowing (the codegen base allows nullable), but the routing logic * in `TaskSubscription.consider` matches on `parent_message_id` so a * spec-violating server emit with `parent_message_id = null` would fail * to route — surfaces as an absent ack from the consumer's POV (the * `sessionId` promise never resolves), not a silent miss. */ export interface TaskStartedEnvelope extends EnvelopeBase { readonly kind: 'task.started'; readonly parent_message_id: MessageId; readonly payload: TaskStartedPayload; } export type TaskContinueEnvelope = Sharpen; export type TaskCancelEnvelope = Sharpen; /** * `task.canceling` envelope — cancel-ack surface. * * Carried as a hand-rolled `EnvelopeBase + { kind, payload }` shape * because the variant is not yet in the vendored schema bundle. When * the schema is bumped and `npm run codegen:qar` regenerates * `./generated/envelope.ts`, fold this back into a `Sharpen<>` over the * codegen variant. */ export interface TaskCancelingEnvelope extends EnvelopeBase { readonly kind: 'task.canceling'; readonly payload: TaskCancelingPayload; } export type TaskResubscribeEnvelope = Sharpen; export type TaskDeltaEnvelope = Sharpen; export type TaskDoneEnvelope = Sharpen; /** * `task.forceResume` envelope — stuck-session recovery primitive (C→S). * Hand-rolled because the variant is not yet in the vendored schema * bundle; same pattern `task.started` uses. * * Built on {@link SharpenStart} (no `session_id` on the wire) because * forceResume operates on the consumer's `idempotency_key` rather than * an already-bound session UUID — the consumer is typically recovering * a session whose UUID it has lost (process restart). QAR derives the * session UUID server-side via the same `uuidv5(QAR_NS_V1, tenant_id + * ":" + idempotency_key)` derivation as `task.start` admission. */ export interface TaskForceResumeEnvelope extends Omit { readonly kind: 'task.forceResume'; readonly payload: TaskForceResumePayload; } /** * `task.force_resumed` envelope — ack for `task.forceResume` (S→C). * * Carries the derived `session_id` (so the SDK can pin the session for * subsequent `task.continue` calls) plus the payload's `task_id` + * `state`. Hand-rolled mirror of `task.started`. */ export interface TaskForceResumedEnvelope extends EnvelopeBase { readonly kind: 'task.force_resumed'; readonly parent_message_id: MessageId; readonly payload: TaskForceResumedPayload; } /** * `tool.request` envelope — `payload` is the parallel-batched overlay * (`calls: ToolCall[]`, required `node_name`) rather than the * pre-amendment codegen shape. Until the schema is bumped, the * generated `ToolRequest['payload']` is still the single-call form; * the `Omit<…, 'payload'> & { payload: ToolRequestPayload }` swap masks * it. */ export type ToolRequestEnvelope = Omit, 'payload'> & { readonly payload: ToolRequestPayload; }; /** `tool.response` envelope — `payload` is the parallel-batched overlay. */ export type ToolResponseEnvelope = Omit, 'payload'> & { readonly payload: ToolResponsePayload; }; export type StateUpdateEnvelope = Sharpen; export type AgentSpawnEnvelope = Sharpen; /** * `tool.activity` envelope — one-way, server→client notification that a * QAR-managed tool ran in-process (which never round-trips through * `tool.request` / `tool.response`). Payload is redacted: `tool_name` + * `outcome` (+ `code` on error), never the bearer or call args. */ export type ToolActivityEnvelope = Sharpen; export type BulletinPostEnvelope = Sharpen; export type ArtifactAddEnvelope = Sharpen; export type FlowPauseEnvelope = Sharpen; export type FlowResumeEnvelope = Sharpen; export type ErrorEnvelope = Sharpen; /** * Common fields on every envelope. Derived from the shape `Sharpen` produces * (intersection of required base fields). Hand-written declaration so the * exported interface is grokable in API docs without chasing the helper. */ export interface EnvelopeBase { readonly envelope_version: EnvelopeVersion; readonly message_id: MessageId; readonly parent_message_id?: MessageId | null; readonly session_id: SessionId; readonly trace_context: import('./generated/envelope.js').TraceContext; readonly ts: string; } /** * The 16-kind envelope discriminated union. Every QAR-side wire message lands here. * * Use `switch (env.kind)` to narrow; `kind` is the single source of truth (no * payload-level discriminator, no two-level nesting). */ export type Envelope = TaskStartEnvelope | TaskStartedEnvelope | TaskContinueEnvelope | TaskCancelEnvelope | TaskCancelingEnvelope | TaskResubscribeEnvelope | TaskDeltaEnvelope | TaskDoneEnvelope | TaskForceResumeEnvelope | TaskForceResumedEnvelope | ToolRequestEnvelope | ToolResponseEnvelope | StateUpdateEnvelope | AgentSpawnEnvelope | ToolActivityEnvelope | BulletinPostEnvelope | ArtifactAddEnvelope | FlowPauseEnvelope | FlowResumeEnvelope | ErrorEnvelope; /** All envelope `kind` values, as a string-literal union. */ export type EnvelopeKind = Envelope['kind']; /** Direction whitelists — kept in sync with QAR's `protocol/envelopes.py` taxonomy. */ export declare const CLIENT_TO_SERVER_KINDS: readonly ["task.start", "task.continue", "task.cancel", "task.resubscribe", "task.forceResume", "tool.response"]; export declare const SERVER_TO_CLIENT_KINDS: readonly ["task.started", "task.delta", "task.done", "task.canceling", "task.force_resumed", "tool.request", "agent.spawn", "tool.activity", "bulletin.post", "flow.pause", "flow.resume", "error"]; /** * Kinds the wire allows in both directions. Mirrors QAR's * `protocol/envelopes.BIDIRECTIONAL_KINDS = {"state.update", "artifact.add"}`. * * **`artifact.add`** is on the QAR-side bidirectional whitelist so a future * SDK client-emitted artifact path (a tool handler attaching a file, a skill * pushing a structured record) can land without a wire-contract change. The * SDK currently does not emit `artifact.add` — `ArtifactsClient` is * observer-only and the outbound-payload union (`OutboundPayload` in * `connection.ts`) does not include `artifact.add`. Aligning the taxonomy * here matches the cross-repo wire contract (per the 2026-05-13 retro) so * the SDK never rejects an inbound `artifact.add` for direction reasons. * Same precedent as `state.update`: bidirectional on the wire, observer-only * on the public surface today. * * If you're auditing where SDK-side emission lands: ArtifactsClient.onArtifact * is the read path; emit lives in a future ticket. */ export declare const BIDIRECTIONAL_KINDS: readonly ["state.update", "artifact.add"]; export type ClientToServerKind = (typeof CLIENT_TO_SERVER_KINDS)[number]; export type ServerToClientKind = (typeof SERVER_TO_CLIENT_KINDS)[number]; export type BidirectionalKind = (typeof BIDIRECTIONAL_KINDS)[number]; export {}; //# sourceMappingURL=envelopes.d.ts.map