@ai-agent-forge/plugin-sdk 0.88.14 → 0.88.16

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.
@@ -1 +1 @@
1
- {"version":3,"file":"observability.js","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuLH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,eAAe;IACf,WAAW;IACX,aAAa;IACb,cAAc;IACd,WAAW;IACX,WAAW;IACX,OAAO;IACP,UAAU;IACV,YAAY;CAC8B,CAAC;AAG5C,MAAM,6BAA6B,GAAuD,IAAI,CAAC","sourcesContent":["/**\n * Public contracts for the first-party observability plugin (event journal +\n * problem reports + trace sink). The plugin package\n * (`@agent-forge/plugin-observability`) is the implementation; this module is\n * the single authoring authority for its public types and the host-facing\n * trace adapter contract. The host re-exports the surface from its historical\n * `plugins/observability-types.ts` path so existing imports keep resolving.\n *\n * Contains types only, plus the `TRACE_KINDS_V1` value mirror with its\n * compile-time drift guards (CLI `--trace-kind` validation consumes the\n * value).\n */\n\nimport type { LoggerRecord, OutputArtifactRef } from \"./public-api.ts\";\n\nexport type ObservabilityLogLevel = \"info\" | \"warn\" | \"error\";\n\n/**\n * Output projection surface carried by the problem report's whole-document\n * projection. Authority moved from the host's `host/output-serializer.ts`\n * (D-075 S4 second batch); the host re-exports it from there.\n */\nexport type HostOutputSurfaceV1 = \"tui\" | \"cli-text\" | \"inspector\";\n\n/** One bounded-journal record derived from a delivered lifecycle event. */\nexport interface ObservabilityJournalEntryV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\t/** Session id extracted from the event data; null when the event has none. */\n\treadonly sessionId: string | null;\n\t/** Lifecycle event type, e.g. \"model.stream\" or \"tool.commit\". */\n\treadonly stage: string;\n\treadonly level: ObservabilityLogLevel;\n\t/** Event correlation id, used as the trace grouping key. */\n\treadonly traceId?: string;\n\treadonly summary: string;\n\t/** Validated lifecycle event data, retained verbatim for diagnosis. */\n\treadonly data?: unknown;\n}\n\nexport interface ObservabilityQueryFilterV1 {\n\treadonly sessionId?: string;\n\treadonly stage?: string;\n\treadonly level?: ObservabilityLogLevel;\n\treadonly traceId?: string;\n\t/** When set, returns the most recent `limit` matching entries in order. */\n\treadonly limit?: number;\n}\n\nexport interface ObservabilityTraceStepV1 {\n\treadonly entry: ObservabilityJournalEntryV1;\n\t/** Milliseconds since the previous step; null on the first step. */\n\treadonly durationMs: number | null;\n}\n\nexport interface ObservabilityTraceTimelineV1 {\n\treadonly traceId: string;\n\treadonly steps: readonly ObservabilityTraceStepV1[];\n}\n\nexport interface ObservabilityJournalStatsV1 {\n\treadonly size: number;\n\treadonly dropped: number;\n}\n\nexport interface ObservabilityProblemReportSessionV1 {\n\treadonly sessionId: string;\n\treadonly entryCount: number;\n\treadonly firstSequence: number | null;\n\treadonly lastSequence: number | null;\n}\n\n/** Full error envelope of the most recent error-level entry for the session. */\nexport interface ObservabilityProblemReportErrorV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\treadonly stage: string;\n\t/** Lifecycle phase when the event carries one (model.stream). */\n\treadonly phase?: string;\n\treadonly traceId: string | null;\n\treadonly code: string;\n\treadonly message: string;\n}\n\nexport interface ObservabilityProblemReportTailEntryV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\treadonly stage: string;\n\treadonly level: ObservabilityLogLevel;\n\treadonly traceId: string | null;\n\treadonly summary: string;\n\t/**\n\t * Payload rendered through the host output serializer redaction channel;\n\t * internal field keys are replaced at any depth before export.\n\t */\n\treadonly data?: string;\n}\n\nexport interface ObservabilityProblemReportCountsV1 {\n\treadonly byStage: Readonly<Record<string, number>>;\n\treadonly byLevel: Readonly<Record<string, number>>;\n}\n\n/** A metric the journal cannot currently source; the reason names the missing event granularity. */\nexport interface MetricsNotSampledV1 {\n\treadonly status: \"not_sampled\";\n\treadonly reason: string;\n}\n\n/** Cancel-latency samples: delay from a cancel signal to the turn's terminal event. */\nexport interface MetricsCancelLatencySampledV1 {\n\treadonly status: \"sampled\";\n\treadonly count: number;\n\treadonly p50Ms: number | null;\n\treadonly p95Ms: number | null;\n}\n\n/** Token totals summed over usage-bearing journal events. */\nexport interface MetricsTokensSampledV1 {\n\treadonly status: \"sampled\";\n\treadonly totalTokens: number;\n}\n\n/** Memory recall hit rate over `memory_recall` tool executions in the window. */\nexport interface MetricsMemoryRecallSampledV1 {\n\treadonly status: \"sampled\";\n\treadonly calls: number;\n\treadonly hits: number;\n\treadonly hitRate: number;\n}\n\nexport interface MetricsToolErrorsV1 {\n\treadonly calls: number;\n\treadonly errors: number;\n\t/** `null` when no tool executions occurred in the window. */\n\treadonly errorRate: number | null;\n\t/** Errors per tool name, descending by count then ascending by name. */\n\treadonly topErrors: readonly { readonly toolName: string; readonly errors: number }[];\n}\n\nexport interface MetricsTurnsV1 {\n\treadonly count: number;\n\treadonly successCount: number;\n\t/** `null` when no turns occurred in the window. */\n\treadonly successRate: number | null;\n\treadonly p50DurationMs: number | null;\n\treadonly p95DurationMs: number | null;\n}\n\n/** Window metadata: which turns were aggregated and their time span. */\nexport interface MetricsWindowV1 {\n\t/** Requested turn bound; `null` when the whole journal was aggregated. */\n\treadonly windowTurns: number | null;\n\treadonly sampledTurns: number;\n\treadonly firstAt: number | null;\n\treadonly lastAt: number | null;\n}\n\n/**\n * Deterministic product metrics aggregated read-only from the journal ring.\n * Metrics the journal cannot source are reported as `not_sampled` with the\n * reason instead of being fabricated.\n */\nexport interface MetricsSnapshotV1 {\n\treadonly version: 1;\n\treadonly window: MetricsWindowV1;\n\treadonly turns: MetricsTurnsV1;\n\treadonly tools: MetricsToolErrorsV1;\n\treadonly cancelLatencyMs: MetricsCancelLatencySampledV1 | MetricsNotSampledV1;\n\treadonly tokens: MetricsTokensSampledV1 | MetricsNotSampledV1;\n\treadonly memoryRecall: MetricsMemoryRecallSampledV1 | MetricsNotSampledV1;\n}\n\n/** Aggregate options for {@link ObservabilityPlugin.getMetrics}. */\nexport interface ObservabilityMetricsOptions {\n\t/** Aggregate only the most recent `windowTurns` correlationId groups; omit for the whole journal. */\n\treadonly windowTurns?: number;\n}\n\n/**\n * Trace record kinds for `agent-forge/trace.v1`\n * (design: docs/design/会话轨迹与日志设计.md §4.2).\n */\nexport type TraceKindV1 =\n\t| \"session.start\"\n\t| \"turn.span\"\n\t| \"llm.request\"\n\t| \"llm.response\"\n\t| \"tool.span\"\n\t| \"lifecycle\"\n\t| \"error\"\n\t| \"decision\"\n\t| \"plugin.log\";\n\n/**\n * Runtime mirror of `TraceKindV1` for value-level consumers (CLI\n * `--trace-kind` validation), kept beside the union so the two compile-time\n * drift guards fail the build when they diverge in either direction:\n * - the `satisfies` below rejects a list entry the union no longer has;\n * - `_TRACE_KINDS_EXHAUSTIVE_CHECK` rejects a union member missing from the\n * list (`never` is not assignable from `true`).\n */\nexport const TRACE_KINDS_V1 = [\n\t\"session.start\",\n\t\"turn.span\",\n\t\"llm.request\",\n\t\"llm.response\",\n\t\"tool.span\",\n\t\"lifecycle\",\n\t\"error\",\n\t\"decision\",\n\t\"plugin.log\",\n] as const satisfies readonly TraceKindV1[];\n\ntype TraceKindListV1 = (typeof TRACE_KINDS_V1)[number];\nconst _TRACE_KINDS_EXHAUSTIVE_CHECK: TraceKindV1 extends TraceKindListV1 ? true : never = true;\n\n/** One append-only JSONL trace record (design §4.2). */\nexport interface TraceRecordV1 {\n\treadonly v: 1;\n\treadonly schema: \"agent-forge/trace.v1\";\n\t/** Monotonic sequence within one trace file, starting at 1. */\n\treadonly seq: number;\n\t/** Host timestamp (epoch ms). */\n\treadonly t: number;\n\t/** Duration of a closed span (ms). */\n\treadonly dur?: number;\n\treadonly kind: TraceKindV1;\n\t/** Turn/request correlation id, shared with lifecycle events and logs. */\n\treadonly traceId?: string;\n\treadonly level?: ObservabilityLogLevel;\n\t/** Kind-specific bounded data. */\n\treadonly data?: unknown;\n}\n\n/** Bounds for trace payload snapshots (design §4.3); each field defaults when omitted. */\nexport interface TraceSinkOptionsV1 {\n\t/** Strings longer than this truncate with a `…[truncated N chars]` suffix. Default 16_000. */\n\treadonly maxStringChars?: number;\n\t/**\n\t * Serialized snapshot budget in bytes, enforced largest-string-first inside\n\t * `llm.request.data.payload`. Strictly below the 512 KiB per-record budget.\n\t * Default 393_216 (384 KiB).\n\t */\n\treadonly maxSnapshotBytes?: number;\n\t/**\n\t * OTLP/HTTP+JSON trace export (design §8). Omitted keeps the exporter\n\t * absent: no transport, no buffering, file-sink behavior unchanged. The\n\t * endpoint is the OTLP base URL (e.g. `http://localhost:4318`); the\n\t * `/v1/traces` signal path is appended unless already present.\n\t */\n\treadonly otlp?: OtlpExportOptionsV1;\n}\n\n/** OTLP/HTTP+JSON exporter options; all durations/bounds are deterministic inputs. */\nexport interface OtlpExportOptionsV1 {\n\t/** OTLP base URL; `/v1/traces` is appended unless already present. */\n\treadonly endpoint: string;\n\t/**\n\t * Transport injection for deterministic tests and custom hosts (proxy,\n\t * in-process collector); defaults to `globalThis.fetch`. Only the fields\n\t * below are consumed by the plugin.\n\t */\n\treadonly fetchImpl?: (\n\t\turl: string,\n\t\tinit: { method: \"POST\"; headers: { \"content-type\": string }; body: string; signal: AbortSignal },\n\t) => Promise<{ ok: boolean; status: number }>;\n\t/** Per-flush HTTP timeout in ms. Default 5_000. */\n\treadonly timeoutMs?: number;\n\t/** Buffered-span count that triggers an early flush. Default 64. */\n\treadonly flushThreshold?: number;\n}\n\n/**\n * Telemetry export seam (design §8): the single attachment point for trace\n * exporters (first OTLP/HTTP+JSON; future Prometheus-style pull surfaces hook\n * here instead of growing bespoke taps). Hosts inject sinks through\n * `ObservabilityTraceConfig.exportSinks`; the plugin never reaches into host\n * internals and never lets a sink break event delivery.\n */\nexport interface ObservabilityExportSinkV1 {\n\t/** Called synchronously for every finalized trace record. Must not throw. */\n\treadonly onTraceRecord: (record: TraceRecordV1) => void;\n\t/** Best-effort flush; resolves after the batch is delivered or dropped. */\n\treadonly flush: (reason: string) => Promise<void>;\n}\n\n/**\n * Host-injected adapter for the trace sink (design §4.4). The host closure\n * owns session-path resolution, provider payload capture, and failure\n * reporting; the plugin never reaches into host internals.\n */\nexport interface ObservabilityHostAdapterV1 {\n\t/** Trace file path for the current session, or `null` when tracing is off. */\n\tgetTraceFilePath(): string | null;\n\t/** Host push of the finalized provider payload (sdk `onPayload` tail). */\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/** Trace write-failure channel; the host forwards to the process log. */\n\tonSinkFailure(error: unknown): void;\n\t/**\n\t * Correlation id of the turn currently in flight, or `undefined` outside a\n\t * turn. Omitted keeps the phase-1 behavior: `llm.request` and `plugin.log`\n\t * records carry no traceId.\n\t */\n\tgetTurnCorrelationId?(): string | undefined;\n\t/**\n\t * Session-header fact: the parent agent session id recorded at session\n\t * creation (`SessionHeader.parentAgentSession`, session-manager.ts).\n\t * Omitted — or returning `undefined` when the header has no such field —\n\t * keeps the previous behavior: the `session.start` record carries no\n\t * `parentSessionId`.\n\t */\n\tgetParentAgentSessionId?(): string | undefined;\n\t/**\n\t * Session-header fact: the parent-turn correlation id that created this\n\t * session (`SessionHeader.parentTraceId`, session-manager.ts; frozen at\n\t * creation). Omitted — or returning `undefined` — keeps the previous\n\t * behavior: the `session.start` record carries no `parentTraceId`.\n\t */\n\tgetParentTraceId?(): string | undefined;\n\t/**\n\t * Plugin-to-host facade registration (D-075 S4 second batch). The plugin\n\t * entry calls it once after creating the plugin instance so the host's\n\t * assembly refs (provider payload capture, plugin.log mirror, /metrics\n\t * source) keep their embedded-era semantics; calling it again with\n\t * `undefined` on teardown clears them. Omitted on hosts that consume the\n\t * facade through another channel — the entry must probe before calling.\n\t */\n\tregisterFacade?(facade: ObservabilityPluginFacadeV1 | undefined): void;\n}\n\n/**\n * The narrow plugin-facing face the host consumes through\n * {@link ObservabilityHostAdapterV1.registerFacade}. The plugin instance is\n * structurally assignable to it.\n */\nexport interface ObservabilityPluginFacadeV1 {\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/** Optional: present since the plugin.log mirror (design §7.2). */\n\tmirrorPluginLog?(record: LoggerRecord): void;\n\tgetMetrics(options?: ObservabilityMetricsOptions): MetricsSnapshotV1;\n}\n\n/** Deterministic problem report; `reportId` is FNV-1a-32 hex over the canonical JSON of the report body. */\nexport interface ProblemReportV1 {\n\treadonly version: 1;\n\treadonly reportId: string;\n\treadonly createdAt: number;\n\treadonly reason: string;\n\treadonly session: ObservabilityProblemReportSessionV1;\n\treadonly lastError: ObservabilityProblemReportErrorV1 | null;\n\treadonly journalTail: readonly ObservabilityProblemReportTailEntryV1[];\n\treadonly counts: ObservabilityProblemReportCountsV1;\n\treadonly dropped: number;\n\treadonly redactionApplied: boolean;\n\t/** Whole-report projection rendered by the host output serializer. */\n\treadonly projection: {\n\t\treadonly surface: HostOutputSurfaceV1;\n\t\treadonly text: string;\n\t};\n\t/** Present only when the host declared the `output-artifacts` feature. */\n\treadonly artifact?: OutputArtifactRef;\n}\n\n/**\n * The observability plugin's public behavior face (journal read side, trace\n * explanation, deterministic metrics, problem-report export). The plugin\n * package returns this shape from its factory; hosts consume it through\n * {@link ObservabilityPluginFacadeV1} or their own references.\n */\nexport interface ObservabilityPlugin {\n\treadonly id: string;\n\tquery(filter?: ObservabilityQueryFilterV1): readonly ObservabilityJournalEntryV1[];\n\texplainTrace(traceId: string): ObservabilityTraceTimelineV1;\n\tgetMetrics(options?: ObservabilityMetricsOptions): MetricsSnapshotV1;\n\tstats(): ObservabilityJournalStatsV1;\n\texportProblemReport(input: { readonly sessionId: string; readonly reason: string }): Promise<ProblemReportV1>;\n\t/**\n\t * Receives the finalized provider payload from the host adapter and emits\n\t * one `llm.request` trace record with a bounded redacted snapshot (design\n\t * §4.3). No-op when no trace adapter was injected.\n\t */\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/**\n\t * Receives one plugin LoggerAPI record forwarded by the host runtime sink\n\t * (`CapabilityRuntimeOptions.onLogRecord`) and appends a `plugin.log` trace\n\t * record (design §4.2/§7.2). No-op when no trace adapter was injected.\n\t * Must never call `api.logger`: the host forwards logger output here, so\n\t * logging from inside would recurse.\n\t */\n\tmirrorPluginLog(record: LoggerRecord): void;\n\tdispose(): void;\n}\n"]}
1
+ {"version":3,"file":"observability.js","sourceRoot":"","sources":["../src/observability.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAuLH;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG;IAC7B,eAAe;IACf,WAAW;IACX,aAAa;IACb,cAAc;IACd,WAAW;IACX,WAAW;IACX,OAAO;IACP,UAAU;IACV,YAAY;CAC8B,CAAC;AAG5C,MAAM,6BAA6B,GAAuD,IAAI,CAAC","sourcesContent":["/**\n * Public contracts for the first-party observability plugin (event journal +\n * problem reports + trace sink). The plugin package\n * (`@agent-forge/plugin-observability`) is the implementation; this module is\n * the single authoring authority for its public types and the host-facing\n * trace adapter contract. The host re-exports the surface from its historical\n * `plugins/observability-types.ts` path so existing imports keep resolving.\n *\n * Contains types only, plus the `TRACE_KINDS_V1` value mirror with its\n * compile-time drift guards (CLI `--trace-kind` validation consumes the\n * value).\n */\n\nimport type { LoggerRecord, OutputArtifactRef } from \"./public-api.ts\";\n\nexport type ObservabilityLogLevel = \"info\" | \"warn\" | \"error\";\n\n/**\n * Output projection surface carried by the problem report's whole-document\n * projection. Authority moved from the host's `host/output-serializer.ts`\n * (D-075 S4 second batch); the host re-exports it from there.\n */\nexport type HostOutputSurfaceV1 = \"tui\" | \"cli-text\" | \"inspector\";\n\n/** One bounded-journal record derived from a delivered lifecycle event. */\nexport interface ObservabilityJournalEntryV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\t/** Session id extracted from the event data; null when the event has none. */\n\treadonly sessionId: string | null;\n\t/** Lifecycle event type, e.g. \"model.stream\" or \"tool.commit\". */\n\treadonly stage: string;\n\treadonly level: ObservabilityLogLevel;\n\t/** Event correlation id, used as the trace grouping key. */\n\treadonly traceId?: string;\n\treadonly summary: string;\n\t/** Validated lifecycle event data, retained verbatim for diagnosis. */\n\treadonly data?: unknown;\n}\n\nexport interface ObservabilityQueryFilterV1 {\n\treadonly sessionId?: string;\n\treadonly stage?: string;\n\treadonly level?: ObservabilityLogLevel;\n\treadonly traceId?: string;\n\t/** When set, returns the most recent `limit` matching entries in order. */\n\treadonly limit?: number;\n}\n\nexport interface ObservabilityTraceStepV1 {\n\treadonly entry: ObservabilityJournalEntryV1;\n\t/** Milliseconds since the previous step; null on the first step. */\n\treadonly durationMs: number | null;\n}\n\nexport interface ObservabilityTraceTimelineV1 {\n\treadonly traceId: string;\n\treadonly steps: readonly ObservabilityTraceStepV1[];\n}\n\nexport interface ObservabilityJournalStatsV1 {\n\treadonly size: number;\n\treadonly dropped: number;\n}\n\nexport interface ObservabilityProblemReportSessionV1 {\n\treadonly sessionId: string;\n\treadonly entryCount: number;\n\treadonly firstSequence: number | null;\n\treadonly lastSequence: number | null;\n}\n\n/** Full error envelope of the most recent error-level entry for the session. */\nexport interface ObservabilityProblemReportErrorV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\treadonly stage: string;\n\t/** Lifecycle phase when the event carries one (model.stream). */\n\treadonly phase?: string;\n\treadonly traceId: string | null;\n\treadonly code: string;\n\treadonly message: string;\n}\n\nexport interface ObservabilityProblemReportTailEntryV1 {\n\treadonly sequence: number;\n\treadonly at: number;\n\treadonly stage: string;\n\treadonly level: ObservabilityLogLevel;\n\treadonly traceId: string | null;\n\treadonly summary: string;\n\t/**\n\t * Payload rendered through the host output serializer redaction channel;\n\t * internal field keys are replaced at any depth before export.\n\t */\n\treadonly data?: string;\n}\n\nexport interface ObservabilityProblemReportCountsV1 {\n\treadonly byStage: Readonly<Record<string, number>>;\n\treadonly byLevel: Readonly<Record<string, number>>;\n}\n\n/** A metric the journal cannot currently source; the reason names the missing event granularity. */\nexport interface MetricsNotSampledV1 {\n\treadonly status: \"not_sampled\";\n\treadonly reason: string;\n}\n\n/** Cancel-latency samples: delay from a cancel signal to the turn's terminal event. */\nexport interface MetricsCancelLatencySampledV1 {\n\treadonly status: \"sampled\";\n\treadonly count: number;\n\treadonly p50Ms: number | null;\n\treadonly p95Ms: number | null;\n}\n\n/** Token totals summed over usage-bearing journal events. */\nexport interface MetricsTokensSampledV1 {\n\treadonly status: \"sampled\";\n\treadonly totalTokens: number;\n}\n\n/** Memory recall hit rate over `memory_recall` tool executions in the window. */\nexport interface MetricsMemoryRecallSampledV1 {\n\treadonly status: \"sampled\";\n\treadonly calls: number;\n\treadonly hits: number;\n\treadonly hitRate: number;\n}\n\nexport interface MetricsToolErrorsV1 {\n\treadonly calls: number;\n\treadonly errors: number;\n\t/** `null` when no tool executions occurred in the window. */\n\treadonly errorRate: number | null;\n\t/** Errors per tool name, descending by count then ascending by name. */\n\treadonly topErrors: readonly { readonly toolName: string; readonly errors: number }[];\n}\n\nexport interface MetricsTurnsV1 {\n\treadonly count: number;\n\treadonly successCount: number;\n\t/** `null` when no turns occurred in the window. */\n\treadonly successRate: number | null;\n\treadonly p50DurationMs: number | null;\n\treadonly p95DurationMs: number | null;\n}\n\n/** Window metadata: which turns were aggregated and their time span. */\nexport interface MetricsWindowV1 {\n\t/** Requested turn bound; `null` when the whole journal was aggregated. */\n\treadonly windowTurns: number | null;\n\treadonly sampledTurns: number;\n\treadonly firstAt: number | null;\n\treadonly lastAt: number | null;\n}\n\n/**\n * Deterministic product metrics aggregated read-only from the journal ring.\n * Metrics the journal cannot source are reported as `not_sampled` with the\n * reason instead of being fabricated.\n */\nexport interface MetricsSnapshotV1 {\n\treadonly version: 1;\n\treadonly window: MetricsWindowV1;\n\treadonly turns: MetricsTurnsV1;\n\treadonly tools: MetricsToolErrorsV1;\n\treadonly cancelLatencyMs: MetricsCancelLatencySampledV1 | MetricsNotSampledV1;\n\treadonly tokens: MetricsTokensSampledV1 | MetricsNotSampledV1;\n\treadonly memoryRecall: MetricsMemoryRecallSampledV1 | MetricsNotSampledV1;\n}\n\n/** Aggregate options for {@link ObservabilityPlugin.getMetrics}. */\nexport interface ObservabilityMetricsOptions {\n\t/** Aggregate only the most recent `windowTurns` correlationId groups; omit for the whole journal. */\n\treadonly windowTurns?: number;\n}\n\n/**\n * Trace record kinds for `agent-forge/trace.v1`\n * (design: docs/design/会话轨迹与日志设计.md §4.2).\n */\nexport type TraceKindV1 =\n\t| \"session.start\"\n\t| \"turn.span\"\n\t| \"llm.request\"\n\t| \"llm.response\"\n\t| \"tool.span\"\n\t| \"lifecycle\"\n\t| \"error\"\n\t| \"decision\"\n\t| \"plugin.log\";\n\n/**\n * Runtime mirror of `TraceKindV1` for value-level consumers (CLI\n * `--trace-kind` validation), kept beside the union so the two compile-time\n * drift guards fail the build when they diverge in either direction:\n * - the `satisfies` below rejects a list entry the union no longer has;\n * - `_TRACE_KINDS_EXHAUSTIVE_CHECK` rejects a union member missing from the\n * list (`never` is not assignable from `true`).\n */\nexport const TRACE_KINDS_V1 = [\n\t\"session.start\",\n\t\"turn.span\",\n\t\"llm.request\",\n\t\"llm.response\",\n\t\"tool.span\",\n\t\"lifecycle\",\n\t\"error\",\n\t\"decision\",\n\t\"plugin.log\",\n] as const satisfies readonly TraceKindV1[];\n\ntype TraceKindListV1 = (typeof TRACE_KINDS_V1)[number];\nconst _TRACE_KINDS_EXHAUSTIVE_CHECK: TraceKindV1 extends TraceKindListV1 ? true : never = true;\n\n/** One append-only JSONL trace record (design §4.2). */\nexport interface TraceRecordV1 {\n\treadonly v: 1;\n\treadonly schema: \"agent-forge/trace.v1\";\n\t/** Monotonic sequence within one trace file, starting at 1. */\n\treadonly seq: number;\n\t/** Host timestamp (epoch ms). */\n\treadonly t: number;\n\t/** Duration of a closed span (ms). */\n\treadonly dur?: number;\n\treadonly kind: TraceKindV1;\n\t/** Turn/request correlation id, shared with lifecycle events and logs. */\n\treadonly traceId?: string;\n\treadonly level?: ObservabilityLogLevel;\n\t/** Kind-specific bounded data. */\n\treadonly data?: unknown;\n}\n\n/** Bounds for trace payload snapshots (design §4.3); each field defaults when omitted. */\nexport interface TraceSinkOptionsV1 {\n\t/** Strings longer than this truncate with a `…[truncated N chars]` suffix. Default 16_000. */\n\treadonly maxStringChars?: number;\n\t/**\n\t * Serialized snapshot budget in bytes, enforced largest-string-first inside\n\t * `llm.request.data.payload`. Strictly below the 512 KiB per-record budget.\n\t * Default 393_216 (384 KiB).\n\t */\n\treadonly maxSnapshotBytes?: number;\n\t/**\n\t * Whether `llm.request.data.payload` records the full provider payload\n\t * snapshot. Default **false** (2026-10-18): the trace is a process record —\n\t * turn/tool/lifecycle order, timings, token usage, errors — and the message\n\t * bodies already live in the session log. A 41.5h run wrote 662MB of\n\t * snapshots (1,975 requests × ~306KB) that duplicated the session file.\n\t * Set true (CLI/SDK: `AGENT_FORGE_TRACE_FULL_PAYLOAD=1`) for forensics.\n\t * Metadata (model, messageCount, toolNames, systemPromptChars) is always\n\t * recorded, and error records are never affected by this switch.\n\t */\n\treadonly includeProviderPayload?: boolean;\n\t/**\n\t * Per-file size ceiling for the trace sink, in bytes. Once the file reaches\n\t * it the collector stops appending and reports a sink failure (the session\n\t * keeps running untraced). A file-count cap cannot bound disk: `sessions/`\n\t * once held 91 trace files — far under the 500-file cap — one of them 694 MB,\n\t * 96.9% of it `llm.request.payload` snapshots (2026-10-21 measurement).\n\t * Default 268_435_456 (256 MiB). Exposed for tests and hosts that need a\n\t * tighter ceiling.\n\t */\n\treadonly maxFileBytes?: number;\n\t/**\n\t * OTLP/HTTP+JSON trace export (design §8). Omitted keeps the exporter\n\t * absent: no transport, no buffering, file-sink behavior unchanged. The\n\t * endpoint is the OTLP base URL (e.g. `http://localhost:4318`); the\n\t * `/v1/traces` signal path is appended unless already present.\n\t */\n\treadonly otlp?: OtlpExportOptionsV1;\n}\n\n/** OTLP/HTTP+JSON exporter options; all durations/bounds are deterministic inputs. */\nexport interface OtlpExportOptionsV1 {\n\t/** OTLP base URL; `/v1/traces` is appended unless already present. */\n\treadonly endpoint: string;\n\t/**\n\t * Transport injection for deterministic tests and custom hosts (proxy,\n\t * in-process collector); defaults to `globalThis.fetch`. Only the fields\n\t * below are consumed by the plugin.\n\t */\n\treadonly fetchImpl?: (\n\t\turl: string,\n\t\tinit: { method: \"POST\"; headers: { \"content-type\": string }; body: string; signal: AbortSignal },\n\t) => Promise<{ ok: boolean; status: number }>;\n\t/** Per-flush HTTP timeout in ms. Default 5_000. */\n\treadonly timeoutMs?: number;\n\t/** Buffered-span count that triggers an early flush. Default 64. */\n\treadonly flushThreshold?: number;\n}\n\n/**\n * Telemetry export seam (design §8): the single attachment point for trace\n * exporters (first OTLP/HTTP+JSON; future Prometheus-style pull surfaces hook\n * here instead of growing bespoke taps). Hosts inject sinks through\n * `ObservabilityTraceConfig.exportSinks`; the plugin never reaches into host\n * internals and never lets a sink break event delivery.\n */\nexport interface ObservabilityExportSinkV1 {\n\t/** Called synchronously for every finalized trace record. Must not throw. */\n\treadonly onTraceRecord: (record: TraceRecordV1) => void;\n\t/** Best-effort flush; resolves after the batch is delivered or dropped. */\n\treadonly flush: (reason: string) => Promise<void>;\n}\n\n/**\n * Host-injected adapter for the trace sink (design §4.4). The host closure\n * owns session-path resolution, provider payload capture, and failure\n * reporting; the plugin never reaches into host internals.\n */\nexport interface ObservabilityHostAdapterV1 {\n\t/** Trace file path for the current session, or `null` when tracing is off. */\n\tgetTraceFilePath(): string | null;\n\t/** Host push of the finalized provider payload (sdk `onPayload` tail). */\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/** Trace write-failure channel; the host forwards to the process log. */\n\tonSinkFailure(error: unknown): void;\n\t/**\n\t * Correlation id of the turn currently in flight, or `undefined` outside a\n\t * turn. Omitted keeps the phase-1 behavior: `llm.request` and `plugin.log`\n\t * records carry no traceId.\n\t */\n\tgetTurnCorrelationId?(): string | undefined;\n\t/**\n\t * Session-header fact: the parent agent session id recorded at session\n\t * creation (`SessionHeader.parentAgentSession`, session-manager.ts).\n\t * Omitted — or returning `undefined` when the header has no such field —\n\t * keeps the previous behavior: the `session.start` record carries no\n\t * `parentSessionId`.\n\t */\n\tgetParentAgentSessionId?(): string | undefined;\n\t/**\n\t * Session-header fact: the parent-turn correlation id that created this\n\t * session (`SessionHeader.parentTraceId`, session-manager.ts; frozen at\n\t * creation). Omitted — or returning `undefined` — keeps the previous\n\t * behavior: the `session.start` record carries no `parentTraceId`.\n\t */\n\tgetParentTraceId?(): string | undefined;\n\t/**\n\t * Plugin-to-host facade registration (D-075 S4 second batch). The plugin\n\t * entry calls it once after creating the plugin instance so the host's\n\t * assembly refs (provider payload capture, plugin.log mirror, /metrics\n\t * source) keep their embedded-era semantics; calling it again with\n\t * `undefined` on teardown clears them. Omitted on hosts that consume the\n\t * facade through another channel — the entry must probe before calling.\n\t */\n\tregisterFacade?(facade: ObservabilityPluginFacadeV1 | undefined): void;\n}\n\n/**\n * The narrow plugin-facing face the host consumes through\n * {@link ObservabilityHostAdapterV1.registerFacade}. The plugin instance is\n * structurally assignable to it.\n */\nexport interface ObservabilityPluginFacadeV1 {\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/** Optional: present since the plugin.log mirror (design §7.2). */\n\tmirrorPluginLog?(record: LoggerRecord): void;\n\tgetMetrics(options?: ObservabilityMetricsOptions): MetricsSnapshotV1;\n}\n\n/** Deterministic problem report; `reportId` is FNV-1a-32 hex over the canonical JSON of the report body. */\nexport interface ProblemReportV1 {\n\treadonly version: 1;\n\treadonly reportId: string;\n\treadonly createdAt: number;\n\treadonly reason: string;\n\treadonly session: ObservabilityProblemReportSessionV1;\n\treadonly lastError: ObservabilityProblemReportErrorV1 | null;\n\treadonly journalTail: readonly ObservabilityProblemReportTailEntryV1[];\n\treadonly counts: ObservabilityProblemReportCountsV1;\n\treadonly dropped: number;\n\treadonly redactionApplied: boolean;\n\t/** Whole-report projection rendered by the host output serializer. */\n\treadonly projection: {\n\t\treadonly surface: HostOutputSurfaceV1;\n\t\treadonly text: string;\n\t};\n\t/** Present only when the host declared the `output-artifacts` feature. */\n\treadonly artifact?: OutputArtifactRef;\n}\n\n/**\n * The observability plugin's public behavior face (journal read side, trace\n * explanation, deterministic metrics, problem-report export). The plugin\n * package returns this shape from its factory; hosts consume it through\n * {@link ObservabilityPluginFacadeV1} or their own references.\n */\nexport interface ObservabilityPlugin {\n\treadonly id: string;\n\tquery(filter?: ObservabilityQueryFilterV1): readonly ObservabilityJournalEntryV1[];\n\texplainTrace(traceId: string): ObservabilityTraceTimelineV1;\n\tgetMetrics(options?: ObservabilityMetricsOptions): MetricsSnapshotV1;\n\tstats(): ObservabilityJournalStatsV1;\n\texportProblemReport(input: { readonly sessionId: string; readonly reason: string }): Promise<ProblemReportV1>;\n\t/**\n\t * Receives the finalized provider payload from the host adapter and emits\n\t * one `llm.request` trace record with a bounded redacted snapshot (design\n\t * §4.3). No-op when no trace adapter was injected.\n\t */\n\tcaptureProviderPayload(payload: unknown, model: unknown): void;\n\t/**\n\t * Receives one plugin LoggerAPI record forwarded by the host runtime sink\n\t * (`CapabilityRuntimeOptions.onLogRecord`) and appends a `plugin.log` trace\n\t * record (design §4.2/§7.2). No-op when no trace adapter was injected.\n\t * Must never call `api.logger`: the host forwards logger output here, so\n\t * logging from inside would recurse.\n\t */\n\tmirrorPluginLog(record: LoggerRecord): void;\n\tdispose(): void;\n}\n"]}
@@ -36,6 +36,21 @@ export interface PluginErrorV1 {
36
36
  readonly diagnosticRef?: ArtifactRefV1;
37
37
  }
38
38
  export type PluginResultStatusV1 = Exclude<PluginOperationStatusV1, "queued" | "running">;
39
+ /**
40
+ * Model-facing content blocks carried by a plugin result (2026-10-21 内容统一决策,
41
+ * design §9.2). `content` is the only channel the model sees: images must ride
42
+ * here as image blocks, never as base64 text inside `summary`/`evidence`.
43
+ */
44
+ export interface PluginTextBlockV1 {
45
+ readonly type: "text";
46
+ readonly text: string;
47
+ }
48
+ export interface PluginImageBlockV1 {
49
+ readonly type: "image";
50
+ readonly data: string;
51
+ readonly mimeType: string;
52
+ }
53
+ export type PluginContentBlockV1 = PluginTextBlockV1 | PluginImageBlockV1;
39
54
  export interface PluginResultV1 {
40
55
  readonly version: 1;
41
56
  readonly operationId: string;
@@ -43,9 +58,22 @@ export interface PluginResultV1 {
43
58
  readonly summary: string;
44
59
  readonly artifacts: readonly ArtifactRefV1[];
45
60
  readonly evidence: readonly EvidenceRefV1[];
61
+ readonly content?: readonly PluginContentBlockV1[];
46
62
  readonly verification?: VerificationV1;
47
63
  readonly error?: PluginErrorV1;
48
64
  }
65
+ /**
66
+ * Evidence summaries are bounded human-readable markers, not a payload channel
67
+ * (2026-10-21 design §9.2). 128 KiB is twice the largest first-party evidence
68
+ * preview (browser DOM 64 KiB), so screenshots or other base64 payloads cannot
69
+ * ride here — they must go through `content` as image blocks.
70
+ */
71
+ export declare const EVIDENCE_SUMMARY_MAX_CHARS_V1: number;
72
+ /**
73
+ * `summary` is the one-line human-readable headline of a result, not a payload
74
+ * channel; long output belongs in `content` text blocks or behind a reference.
75
+ */
76
+ export declare const PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1: number;
49
77
  export declare function validatePluginOperationV1(value: unknown, label?: string): PluginOperationV1;
50
78
  export declare function buildPluginOperationV1(input: PluginOperationV1): PluginOperationV1;
51
79
  export declare function validateEvidenceRefV1(value: unknown, label?: string): EvidenceRefV1;
@@ -54,6 +82,11 @@ export declare function validateVerificationV1(value: unknown, label?: string):
54
82
  export declare function buildVerificationV1(input: VerificationV1): VerificationV1;
55
83
  export declare function validatePluginErrorV1(value: unknown, label?: string): PluginErrorV1;
56
84
  export declare function buildPluginErrorV1(input: PluginErrorV1): PluginErrorV1;
85
+ /** Upper bound on model-facing content blocks a single plugin result may carry. */
86
+ export declare const PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1 = 32;
87
+ export declare function validatePluginContentBlockV1(value: unknown, label?: string): PluginContentBlockV1;
88
+ export declare function validatePluginContentArray(value: unknown, label?: string): readonly PluginContentBlockV1[];
89
+ export declare function buildPluginContentBlockV1(input: PluginContentBlockV1): PluginContentBlockV1;
57
90
  export declare function validatePluginResultV1(value: unknown, label?: string): PluginResultV1;
58
91
  export declare function buildPluginResultV1(input: PluginResultV1): PluginResultV1;
59
92
  export declare function assertJsonResultInputV1(value: unknown, label?: string): JsonValue;
@@ -1 +1 @@
1
- {"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertJsonValue,\n\tassertNonEmptyString,\n\tassertOptionalString,\n\tassertPlainRecord,\n\tdetachedFreeze,\n\ttype JsonValue,\n\tsortByKey,\n} from \"./common.ts\";\n\nexport type PluginOperationStatusV1 =\n\t| \"queued\"\n\t| \"running\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"cancelled\"\n\t| \"needs_user\"\n\t| \"unsupported\";\n\nexport const PLUGIN_OPERATION_TERMINAL_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nexport interface PluginOperationV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly pluginId: string;\n\treadonly kind: string;\n\treadonly status: PluginOperationStatusV1;\n\treadonly inputArtifacts?: readonly ArtifactRefV1[];\n\treadonly outputArtifacts?: readonly ArtifactRefV1[];\n\treadonly startedAt?: string;\n\treadonly endedAt?: string;\n}\n\nexport type EvidenceKindV1 =\n\t| \"screenshot\"\n\t| \"dom\"\n\t| \"render\"\n\t| \"diff\"\n\t| \"log\"\n\t| \"assertion\"\n\t| \"trace\"\n\t| \"network\"\n\t| \"structure\";\n\nexport interface EvidenceRefV1 {\n\treadonly version: 1;\n\treadonly evidenceId: string;\n\treadonly kind: EvidenceKindV1;\n\treadonly operationId: string;\n\treadonly artifactRef?: ArtifactRefV1;\n\treadonly summary?: string;\n\treadonly observedAt?: string;\n}\n\nexport interface VerificationV1 {\n\treadonly version: 1;\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface PluginErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n\treadonly diagnosticRef?: ArtifactRefV1;\n}\n\nexport type PluginResultStatusV1 = Exclude<PluginOperationStatusV1, \"queued\" | \"running\">;\n\nexport interface PluginResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly artifacts: readonly ArtifactRefV1[];\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nconst EVIDENCE_KINDS_V1: readonly EvidenceKindV1[] = [\n\t\"screenshot\",\n\t\"dom\",\n\t\"render\",\n\t\"diff\",\n\t\"log\",\n\t\"assertion\",\n\t\"trace\",\n\t\"network\",\n\t\"structure\",\n];\nconst OPERATION_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"queued\",\n\t\"running\",\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nfunction validateStatus(value: unknown, label: string, values: readonly string[]): string {\n\tif (typeof value !== \"string\" || !values.includes(value)) throw new Error(`${label} is invalid`);\n\treturn value;\n}\n\nfunction validateIsoLike(value: unknown, label: string): string | undefined {\n\tif (value === undefined) return undefined;\n\tassertNonEmptyString(value, label);\n\treturn value;\n}\n\nfunction validateArtifactArray(value: unknown, label: string): readonly ArtifactRefV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tconst result = value.map((item, index) => validateArtifactRefV1(item, `${label}[${index}]`));\n\tif (new Set(result.map((item) => item.artifactRef)).size !== result.length) {\n\t\tthrow new Error(`${label} must not contain duplicate artifactRef values`);\n\t}\n\treturn Object.freeze(sortByKey(result, (item) => item.artifactRef));\n}\n\nexport function validatePluginOperationV1(value: unknown, label = \"Plugin operation\"): PluginOperationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"pluginId\", \"kind\", \"status\"],\n\t\t[\"inputArtifacts\", \"outputArtifacts\", \"startedAt\", \"endedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tassertNonEmptyString(value.pluginId, `${label}.pluginId`);\n\tassertNonEmptyString(value.kind, `${label}.kind`);\n\tconst status = validateStatus(value.status, `${label}.status`, OPERATION_STATUSES_V1) as PluginOperationStatusV1;\n\tconst inputArtifacts =\n\t\tvalue.inputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.inputArtifacts, `${label}.inputArtifacts`);\n\tconst outputArtifacts =\n\t\tvalue.outputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.outputArtifacts, `${label}.outputArtifacts`);\n\tconst startedAt = validateIsoLike(value.startedAt, `${label}.startedAt`);\n\tconst endedAt = validateIsoLike(value.endedAt, `${label}.endedAt`);\n\tif (endedAt !== undefined && startedAt === undefined) throw new Error(`${label}.endedAt requires startedAt`);\n\tif (status === \"running\" && endedAt !== undefined) throw new Error(`${label}.running must not have endedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tpluginId: value.pluginId,\n\t\tkind: value.kind,\n\t\tstatus,\n\t\t...(inputArtifacts === undefined ? {} : { inputArtifacts }),\n\t\t...(outputArtifacts === undefined ? {} : { outputArtifacts }),\n\t\t...(startedAt === undefined ? {} : { startedAt }),\n\t\t...(endedAt === undefined ? {} : { endedAt }),\n\t});\n}\n\nexport function buildPluginOperationV1(input: PluginOperationV1): PluginOperationV1 {\n\treturn validatePluginOperationV1(input);\n}\n\nexport function validateEvidenceRefV1(value: unknown, label = \"Evidence reference\"): EvidenceRefV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"evidenceId\", \"kind\", \"operationId\"],\n\t\t[\"artifactRef\", \"summary\", \"observedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.evidenceId, `${label}.evidenceId`);\n\tconst kind = validateStatus(value.kind, `${label}.kind`, EVIDENCE_KINDS_V1) as EvidenceKindV1;\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst artifactRef =\n\t\tvalue.artifactRef === undefined ? undefined : validateArtifactRefV1(value.artifactRef, `${label}.artifactRef`);\n\tassertOptionalString(value.summary, `${label}.summary`);\n\tconst observedAt = validateIsoLike(value.observedAt, `${label}.observedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tevidenceId: value.evidenceId,\n\t\tkind,\n\t\toperationId: value.operationId,\n\t\t...(artifactRef === undefined ? {} : { artifactRef }),\n\t\t...(value.summary === undefined ? {} : { summary: value.summary }),\n\t\t...(observedAt === undefined ? {} : { observedAt }),\n\t});\n}\n\nexport function buildEvidenceRefV1(input: EvidenceRefV1): EvidenceRefV1 {\n\treturn validateEvidenceRefV1(input);\n}\n\nexport function validateVerificationV1(value: unknown, label = \"Verification\"): VerificationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"status\", \"checks\"], [], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tconst status = validateStatus(value.status, `${label}.status`, [\n\t\t\"passed\",\n\t\t\"failed\",\n\t\t\"unknown\",\n\t]) as VerificationV1[\"status\"];\n\tif (!Array.isArray(value.checks) || value.checks.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(`${label}.checks must contain non-empty strings`);\n\t}\n\tif (new Set(value.checks).size !== value.checks.length)\n\t\tthrow new Error(`${label}.checks must not contain duplicates`);\n\treturn detachedFreeze({ version: 1, status, checks: [...value.checks].sort() });\n}\n\nexport function buildVerificationV1(input: VerificationV1): VerificationV1 {\n\treturn validateVerificationV1(input);\n}\n\nexport function validatePluginErrorV1(value: unknown, label = \"Plugin error\"): PluginErrorV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"code\", \"message\"], [\"causeCode\", \"diagnosticRef\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.code, `${label}.code`);\n\tassertNonEmptyString(value.message, `${label}.message`);\n\tassertOptionalString(value.causeCode, `${label}.causeCode`);\n\tconst diagnosticRef =\n\t\tvalue.diagnosticRef === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactRefV1(value.diagnosticRef, `${label}.diagnosticRef`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tcode: value.code,\n\t\tmessage: value.message,\n\t\t...(value.causeCode === undefined ? {} : { causeCode: value.causeCode }),\n\t\t...(diagnosticRef === undefined ? {} : { diagnosticRef }),\n\t});\n}\n\nexport function buildPluginErrorV1(input: PluginErrorV1): PluginErrorV1 {\n\treturn validatePluginErrorV1(input);\n}\n\nexport function validatePluginResultV1(value: unknown, label = \"Plugin result\"): PluginResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"summary\", \"artifacts\", \"evidence\"],\n\t\t[\"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst status = validateStatus(value.status, `${label}.status`, RESULT_STATUSES_V1) as PluginResultStatusV1;\n\tassertNonEmptyString(value.summary, `${label}.summary`);\n\tconst artifacts = validateArtifactArray(value.artifacts, `${label}.artifacts`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tconst orderedEvidence = Object.freeze(sortByKey(evidence, (item) => item.evidenceId));\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (status !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${status}`);\n\tif (status === \"succeeded\" && error !== undefined) throw new Error(`${label}.succeeded must not include error`);\n\tif (status === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tstatus,\n\t\tsummary: value.summary,\n\t\tartifacts,\n\t\tevidence: orderedEvidence,\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t});\n}\n\nexport function buildPluginResultV1(input: PluginResultV1): PluginResultV1 {\n\treturn validatePluginResultV1(input);\n}\n\nexport function assertJsonResultInputV1(value: unknown, label = \"Result input\"): JsonValue {\n\tassertJsonValue(value, label);\n\treturn detachedFreeze(value);\n}\n"]}
1
+ {"version":3,"file":"operation.d.ts","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertJsonValue,\n\tassertNonEmptyString,\n\tassertOptionalString,\n\tassertPlainRecord,\n\tdetachedFreeze,\n\ttype JsonValue,\n\tsortByKey,\n} from \"./common.ts\";\n\nexport type PluginOperationStatusV1 =\n\t| \"queued\"\n\t| \"running\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"cancelled\"\n\t| \"needs_user\"\n\t| \"unsupported\";\n\nexport const PLUGIN_OPERATION_TERMINAL_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nexport interface PluginOperationV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly pluginId: string;\n\treadonly kind: string;\n\treadonly status: PluginOperationStatusV1;\n\treadonly inputArtifacts?: readonly ArtifactRefV1[];\n\treadonly outputArtifacts?: readonly ArtifactRefV1[];\n\treadonly startedAt?: string;\n\treadonly endedAt?: string;\n}\n\nexport type EvidenceKindV1 =\n\t| \"screenshot\"\n\t| \"dom\"\n\t| \"render\"\n\t| \"diff\"\n\t| \"log\"\n\t| \"assertion\"\n\t| \"trace\"\n\t| \"network\"\n\t| \"structure\";\n\nexport interface EvidenceRefV1 {\n\treadonly version: 1;\n\treadonly evidenceId: string;\n\treadonly kind: EvidenceKindV1;\n\treadonly operationId: string;\n\treadonly artifactRef?: ArtifactRefV1;\n\treadonly summary?: string;\n\treadonly observedAt?: string;\n}\n\nexport interface VerificationV1 {\n\treadonly version: 1;\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface PluginErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n\treadonly diagnosticRef?: ArtifactRefV1;\n}\n\nexport type PluginResultStatusV1 = Exclude<PluginOperationStatusV1, \"queued\" | \"running\">;\n\n/**\n * Model-facing content blocks carried by a plugin result (2026-10-21 内容统一决策,\n * design §9.2). `content` is the only channel the model sees: images must ride\n * here as image blocks, never as base64 text inside `summary`/`evidence`.\n */\nexport interface PluginTextBlockV1 {\n\treadonly type: \"text\";\n\treadonly text: string;\n}\n\nexport interface PluginImageBlockV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\nexport type PluginContentBlockV1 = PluginTextBlockV1 | PluginImageBlockV1;\n\nexport interface PluginResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly artifacts: readonly ArtifactRefV1[];\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly content?: readonly PluginContentBlockV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nconst EVIDENCE_KINDS_V1: readonly EvidenceKindV1[] = [\n\t\"screenshot\",\n\t\"dom\",\n\t\"render\",\n\t\"diff\",\n\t\"log\",\n\t\"assertion\",\n\t\"trace\",\n\t\"network\",\n\t\"structure\",\n];\nconst OPERATION_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"queued\",\n\t\"running\",\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\n/**\n * Evidence summaries are bounded human-readable markers, not a payload channel\n * (2026-10-21 design §9.2). 128 KiB is twice the largest first-party evidence\n * preview (browser DOM 64 KiB), so screenshots or other base64 payloads cannot\n * ride here — they must go through `content` as image blocks.\n */\nexport const EVIDENCE_SUMMARY_MAX_CHARS_V1 = 128 * 1024;\n\n/**\n * `summary` is the one-line human-readable headline of a result, not a payload\n * channel; long output belongs in `content` text blocks or behind a reference.\n */\nexport const PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1 = 8 * 1024;\n\nfunction validateStatus(value: unknown, label: string, values: readonly string[]): string {\n\tif (typeof value !== \"string\" || !values.includes(value)) throw new Error(`${label} is invalid`);\n\treturn value;\n}\n\nfunction validateIsoLike(value: unknown, label: string): string | undefined {\n\tif (value === undefined) return undefined;\n\tassertNonEmptyString(value, label);\n\treturn value;\n}\n\nfunction validateArtifactArray(value: unknown, label: string): readonly ArtifactRefV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tconst result = value.map((item, index) => validateArtifactRefV1(item, `${label}[${index}]`));\n\tif (new Set(result.map((item) => item.artifactRef)).size !== result.length) {\n\t\tthrow new Error(`${label} must not contain duplicate artifactRef values`);\n\t}\n\treturn Object.freeze(sortByKey(result, (item) => item.artifactRef));\n}\n\nexport function validatePluginOperationV1(value: unknown, label = \"Plugin operation\"): PluginOperationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"pluginId\", \"kind\", \"status\"],\n\t\t[\"inputArtifacts\", \"outputArtifacts\", \"startedAt\", \"endedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tassertNonEmptyString(value.pluginId, `${label}.pluginId`);\n\tassertNonEmptyString(value.kind, `${label}.kind`);\n\tconst status = validateStatus(value.status, `${label}.status`, OPERATION_STATUSES_V1) as PluginOperationStatusV1;\n\tconst inputArtifacts =\n\t\tvalue.inputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.inputArtifacts, `${label}.inputArtifacts`);\n\tconst outputArtifacts =\n\t\tvalue.outputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.outputArtifacts, `${label}.outputArtifacts`);\n\tconst startedAt = validateIsoLike(value.startedAt, `${label}.startedAt`);\n\tconst endedAt = validateIsoLike(value.endedAt, `${label}.endedAt`);\n\tif (endedAt !== undefined && startedAt === undefined) throw new Error(`${label}.endedAt requires startedAt`);\n\tif (status === \"running\" && endedAt !== undefined) throw new Error(`${label}.running must not have endedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tpluginId: value.pluginId,\n\t\tkind: value.kind,\n\t\tstatus,\n\t\t...(inputArtifacts === undefined ? {} : { inputArtifacts }),\n\t\t...(outputArtifacts === undefined ? {} : { outputArtifacts }),\n\t\t...(startedAt === undefined ? {} : { startedAt }),\n\t\t...(endedAt === undefined ? {} : { endedAt }),\n\t});\n}\n\nexport function buildPluginOperationV1(input: PluginOperationV1): PluginOperationV1 {\n\treturn validatePluginOperationV1(input);\n}\n\nexport function validateEvidenceRefV1(value: unknown, label = \"Evidence reference\"): EvidenceRefV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"evidenceId\", \"kind\", \"operationId\"],\n\t\t[\"artifactRef\", \"summary\", \"observedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.evidenceId, `${label}.evidenceId`);\n\tconst kind = validateStatus(value.kind, `${label}.kind`, EVIDENCE_KINDS_V1) as EvidenceKindV1;\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst artifactRef =\n\t\tvalue.artifactRef === undefined ? undefined : validateArtifactRefV1(value.artifactRef, `${label}.artifactRef`);\n\tassertOptionalString(value.summary, `${label}.summary`);\n\tif (value.summary !== undefined && value.summary.length > EVIDENCE_SUMMARY_MAX_CHARS_V1) {\n\t\tthrow new Error(\n\t\t\t`${label}.summary must not exceed ${EVIDENCE_SUMMARY_MAX_CHARS_V1} characters; carry model-facing content in content blocks`,\n\t\t);\n\t}\n\tconst observedAt = validateIsoLike(value.observedAt, `${label}.observedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tevidenceId: value.evidenceId,\n\t\tkind,\n\t\toperationId: value.operationId,\n\t\t...(artifactRef === undefined ? {} : { artifactRef }),\n\t\t...(value.summary === undefined ? {} : { summary: value.summary }),\n\t\t...(observedAt === undefined ? {} : { observedAt }),\n\t});\n}\n\nexport function buildEvidenceRefV1(input: EvidenceRefV1): EvidenceRefV1 {\n\treturn validateEvidenceRefV1(input);\n}\n\nexport function validateVerificationV1(value: unknown, label = \"Verification\"): VerificationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"status\", \"checks\"], [], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tconst status = validateStatus(value.status, `${label}.status`, [\n\t\t\"passed\",\n\t\t\"failed\",\n\t\t\"unknown\",\n\t]) as VerificationV1[\"status\"];\n\tif (!Array.isArray(value.checks) || value.checks.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(`${label}.checks must contain non-empty strings`);\n\t}\n\tif (new Set(value.checks).size !== value.checks.length)\n\t\tthrow new Error(`${label}.checks must not contain duplicates`);\n\treturn detachedFreeze({ version: 1, status, checks: [...value.checks].sort() });\n}\n\nexport function buildVerificationV1(input: VerificationV1): VerificationV1 {\n\treturn validateVerificationV1(input);\n}\n\nexport function validatePluginErrorV1(value: unknown, label = \"Plugin error\"): PluginErrorV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"code\", \"message\"], [\"causeCode\", \"diagnosticRef\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.code, `${label}.code`);\n\tassertNonEmptyString(value.message, `${label}.message`);\n\tassertOptionalString(value.causeCode, `${label}.causeCode`);\n\tconst diagnosticRef =\n\t\tvalue.diagnosticRef === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactRefV1(value.diagnosticRef, `${label}.diagnosticRef`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tcode: value.code,\n\t\tmessage: value.message,\n\t\t...(value.causeCode === undefined ? {} : { causeCode: value.causeCode }),\n\t\t...(diagnosticRef === undefined ? {} : { diagnosticRef }),\n\t});\n}\n\nexport function buildPluginErrorV1(input: PluginErrorV1): PluginErrorV1 {\n\treturn validatePluginErrorV1(input);\n}\n\n/** Upper bound on model-facing content blocks a single plugin result may carry. */\nexport const PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1 = 32;\n\nexport function validatePluginContentBlockV1(value: unknown, label = \"Plugin content block\"): PluginContentBlockV1 {\n\tassertPlainRecord(value, label);\n\tif (value.type === \"text\") {\n\t\tassertExactKeys(value, [\"type\", \"text\"], [], label);\n\t\tassertNonEmptyString(value.text, `${label}.text`);\n\t\treturn detachedFreeze({ type: \"text\" as const, text: value.text });\n\t}\n\tif (value.type === \"image\") {\n\t\tassertExactKeys(value, [\"type\", \"data\", \"mimeType\"], [], label);\n\t\tassertNonEmptyString(value.data, `${label}.data`);\n\t\tassertNonEmptyString(value.mimeType, `${label}.mimeType`);\n\t\tif (!value.mimeType.startsWith(\"image/\")) throw new Error(`${label}.mimeType must be an image media type`);\n\t\treturn detachedFreeze({ type: \"image\" as const, data: value.data, mimeType: value.mimeType });\n\t}\n\tthrow new Error(`${label}.type must be \"text\" or \"image\"`);\n}\n\nexport function validatePluginContentArray(value: unknown, label = \"Plugin content\"): readonly PluginContentBlockV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tif (value.length > PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1)\n\t\tthrow new Error(`${label} must not exceed ${PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1} blocks`);\n\treturn Object.freeze(value.map((item, index) => validatePluginContentBlockV1(item, `${label}[${index}]`)));\n}\n\nexport function buildPluginContentBlockV1(input: PluginContentBlockV1): PluginContentBlockV1 {\n\treturn validatePluginContentBlockV1(input);\n}\n\nexport function validatePluginResultV1(value: unknown, label = \"Plugin result\"): PluginResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"summary\", \"artifacts\", \"evidence\"],\n\t\t[\"content\", \"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst status = validateStatus(value.status, `${label}.status`, RESULT_STATUSES_V1) as PluginResultStatusV1;\n\tassertNonEmptyString(value.summary, `${label}.summary`);\n\tif (value.summary.length > PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1) {\n\t\tthrow new Error(\n\t\t\t`${label}.summary must not exceed ${PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1} characters; put long output in content blocks`,\n\t\t);\n\t}\n\tconst artifacts = validateArtifactArray(value.artifacts, `${label}.artifacts`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tconst orderedEvidence = Object.freeze(sortByKey(evidence, (item) => item.evidenceId));\n\tconst content =\n\t\tvalue.content === undefined ? undefined : validatePluginContentArray(value.content, `${label}.content`);\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (status !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${status}`);\n\tif (status === \"succeeded\" && error !== undefined) throw new Error(`${label}.succeeded must not include error`);\n\tif (status === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tstatus,\n\t\tsummary: value.summary,\n\t\tartifacts,\n\t\tevidence: orderedEvidence,\n\t\t...(content === undefined ? {} : { content }),\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t});\n}\n\nexport function buildPluginResultV1(input: PluginResultV1): PluginResultV1 {\n\treturn validatePluginResultV1(input);\n}\n\nexport function assertJsonResultInputV1(value: unknown, label = \"Result input\"): JsonValue {\n\tassertJsonValue(value, label);\n\treturn detachedFreeze(value);\n}\n"]}
package/dist/operation.js CHANGED
@@ -34,6 +34,18 @@ const RESULT_STATUSES_V1 = [
34
34
  "needs_user",
35
35
  "unsupported",
36
36
  ];
37
+ /**
38
+ * Evidence summaries are bounded human-readable markers, not a payload channel
39
+ * (2026-10-21 design §9.2). 128 KiB is twice the largest first-party evidence
40
+ * preview (browser DOM 64 KiB), so screenshots or other base64 payloads cannot
41
+ * ride here — they must go through `content` as image blocks.
42
+ */
43
+ export const EVIDENCE_SUMMARY_MAX_CHARS_V1 = 128 * 1024;
44
+ /**
45
+ * `summary` is the one-line human-readable headline of a result, not a payload
46
+ * channel; long output belongs in `content` text blocks or behind a reference.
47
+ */
48
+ export const PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1 = 8 * 1024;
37
49
  function validateStatus(value, label, values) {
38
50
  if (typeof value !== "string" || !values.includes(value))
39
51
  throw new Error(`${label} is invalid`);
@@ -100,6 +112,9 @@ export function validateEvidenceRefV1(value, label = "Evidence reference") {
100
112
  assertNonEmptyString(value.operationId, `${label}.operationId`);
101
113
  const artifactRef = value.artifactRef === undefined ? undefined : validateArtifactRefV1(value.artifactRef, `${label}.artifactRef`);
102
114
  assertOptionalString(value.summary, `${label}.summary`);
115
+ if (value.summary !== undefined && value.summary.length > EVIDENCE_SUMMARY_MAX_CHARS_V1) {
116
+ throw new Error(`${label}.summary must not exceed ${EVIDENCE_SUMMARY_MAX_CHARS_V1} characters; carry model-facing content in content blocks`);
117
+ }
103
118
  const observedAt = validateIsoLike(value.observedAt, `${label}.observedAt`);
104
119
  return detachedFreeze({
105
120
  version: 1,
@@ -156,14 +171,46 @@ export function validatePluginErrorV1(value, label = "Plugin error") {
156
171
  export function buildPluginErrorV1(input) {
157
172
  return validatePluginErrorV1(input);
158
173
  }
174
+ /** Upper bound on model-facing content blocks a single plugin result may carry. */
175
+ export const PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1 = 32;
176
+ export function validatePluginContentBlockV1(value, label = "Plugin content block") {
177
+ assertPlainRecord(value, label);
178
+ if (value.type === "text") {
179
+ assertExactKeys(value, ["type", "text"], [], label);
180
+ assertNonEmptyString(value.text, `${label}.text`);
181
+ return detachedFreeze({ type: "text", text: value.text });
182
+ }
183
+ if (value.type === "image") {
184
+ assertExactKeys(value, ["type", "data", "mimeType"], [], label);
185
+ assertNonEmptyString(value.data, `${label}.data`);
186
+ assertNonEmptyString(value.mimeType, `${label}.mimeType`);
187
+ if (!value.mimeType.startsWith("image/"))
188
+ throw new Error(`${label}.mimeType must be an image media type`);
189
+ return detachedFreeze({ type: "image", data: value.data, mimeType: value.mimeType });
190
+ }
191
+ throw new Error(`${label}.type must be "text" or "image"`);
192
+ }
193
+ export function validatePluginContentArray(value, label = "Plugin content") {
194
+ if (!Array.isArray(value))
195
+ throw new Error(`${label} must be an array`);
196
+ if (value.length > PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1)
197
+ throw new Error(`${label} must not exceed ${PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1} blocks`);
198
+ return Object.freeze(value.map((item, index) => validatePluginContentBlockV1(item, `${label}[${index}]`)));
199
+ }
200
+ export function buildPluginContentBlockV1(input) {
201
+ return validatePluginContentBlockV1(input);
202
+ }
159
203
  export function validatePluginResultV1(value, label = "Plugin result") {
160
204
  assertPlainRecord(value, label);
161
- assertExactKeys(value, ["version", "operationId", "status", "summary", "artifacts", "evidence"], ["verification", "error"], label);
205
+ assertExactKeys(value, ["version", "operationId", "status", "summary", "artifacts", "evidence"], ["content", "verification", "error"], label);
162
206
  if (value.version !== 1)
163
207
  throw new Error(`${label}.version must be 1`);
164
208
  assertNonEmptyString(value.operationId, `${label}.operationId`);
165
209
  const status = validateStatus(value.status, `${label}.status`, RESULT_STATUSES_V1);
166
210
  assertNonEmptyString(value.summary, `${label}.summary`);
211
+ if (value.summary.length > PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1) {
212
+ throw new Error(`${label}.summary must not exceed ${PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1} characters; put long output in content blocks`);
213
+ }
167
214
  const artifacts = validateArtifactArray(value.artifacts, `${label}.artifacts`);
168
215
  if (!Array.isArray(value.evidence))
169
216
  throw new Error(`${label}.evidence must be an array`);
@@ -171,6 +218,7 @@ export function validatePluginResultV1(value, label = "Plugin result") {
171
218
  if (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)
172
219
  throw new Error(`${label}.evidence must not contain duplicate evidenceId values`);
173
220
  const orderedEvidence = Object.freeze(sortByKey(evidence, (item) => item.evidenceId));
221
+ const content = value.content === undefined ? undefined : validatePluginContentArray(value.content, `${label}.content`);
174
222
  const verification = value.verification === undefined
175
223
  ? undefined
176
224
  : validateVerificationV1(value.verification, `${label}.verification`);
@@ -188,6 +236,7 @@ export function validatePluginResultV1(value, label = "Plugin result") {
188
236
  summary: value.summary,
189
237
  artifacts,
190
238
  evidence: orderedEvidence,
239
+ ...(content === undefined ? {} : { content }),
191
240
  ...(verification === undefined ? {} : { verification }),
192
241
  ...(error === undefined ? {} : { error }),
193
242
  });
@@ -1 +1 @@
1
- {"version":3,"file":"operation.js","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertJsonValue,\n\tassertNonEmptyString,\n\tassertOptionalString,\n\tassertPlainRecord,\n\tdetachedFreeze,\n\ttype JsonValue,\n\tsortByKey,\n} from \"./common.ts\";\n\nexport type PluginOperationStatusV1 =\n\t| \"queued\"\n\t| \"running\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"cancelled\"\n\t| \"needs_user\"\n\t| \"unsupported\";\n\nexport const PLUGIN_OPERATION_TERMINAL_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nexport interface PluginOperationV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly pluginId: string;\n\treadonly kind: string;\n\treadonly status: PluginOperationStatusV1;\n\treadonly inputArtifacts?: readonly ArtifactRefV1[];\n\treadonly outputArtifacts?: readonly ArtifactRefV1[];\n\treadonly startedAt?: string;\n\treadonly endedAt?: string;\n}\n\nexport type EvidenceKindV1 =\n\t| \"screenshot\"\n\t| \"dom\"\n\t| \"render\"\n\t| \"diff\"\n\t| \"log\"\n\t| \"assertion\"\n\t| \"trace\"\n\t| \"network\"\n\t| \"structure\";\n\nexport interface EvidenceRefV1 {\n\treadonly version: 1;\n\treadonly evidenceId: string;\n\treadonly kind: EvidenceKindV1;\n\treadonly operationId: string;\n\treadonly artifactRef?: ArtifactRefV1;\n\treadonly summary?: string;\n\treadonly observedAt?: string;\n}\n\nexport interface VerificationV1 {\n\treadonly version: 1;\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface PluginErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n\treadonly diagnosticRef?: ArtifactRefV1;\n}\n\nexport type PluginResultStatusV1 = Exclude<PluginOperationStatusV1, \"queued\" | \"running\">;\n\nexport interface PluginResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly artifacts: readonly ArtifactRefV1[];\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nconst EVIDENCE_KINDS_V1: readonly EvidenceKindV1[] = [\n\t\"screenshot\",\n\t\"dom\",\n\t\"render\",\n\t\"diff\",\n\t\"log\",\n\t\"assertion\",\n\t\"trace\",\n\t\"network\",\n\t\"structure\",\n];\nconst OPERATION_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"queued\",\n\t\"running\",\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nfunction validateStatus(value: unknown, label: string, values: readonly string[]): string {\n\tif (typeof value !== \"string\" || !values.includes(value)) throw new Error(`${label} is invalid`);\n\treturn value;\n}\n\nfunction validateIsoLike(value: unknown, label: string): string | undefined {\n\tif (value === undefined) return undefined;\n\tassertNonEmptyString(value, label);\n\treturn value;\n}\n\nfunction validateArtifactArray(value: unknown, label: string): readonly ArtifactRefV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tconst result = value.map((item, index) => validateArtifactRefV1(item, `${label}[${index}]`));\n\tif (new Set(result.map((item) => item.artifactRef)).size !== result.length) {\n\t\tthrow new Error(`${label} must not contain duplicate artifactRef values`);\n\t}\n\treturn Object.freeze(sortByKey(result, (item) => item.artifactRef));\n}\n\nexport function validatePluginOperationV1(value: unknown, label = \"Plugin operation\"): PluginOperationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"pluginId\", \"kind\", \"status\"],\n\t\t[\"inputArtifacts\", \"outputArtifacts\", \"startedAt\", \"endedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tassertNonEmptyString(value.pluginId, `${label}.pluginId`);\n\tassertNonEmptyString(value.kind, `${label}.kind`);\n\tconst status = validateStatus(value.status, `${label}.status`, OPERATION_STATUSES_V1) as PluginOperationStatusV1;\n\tconst inputArtifacts =\n\t\tvalue.inputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.inputArtifacts, `${label}.inputArtifacts`);\n\tconst outputArtifacts =\n\t\tvalue.outputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.outputArtifacts, `${label}.outputArtifacts`);\n\tconst startedAt = validateIsoLike(value.startedAt, `${label}.startedAt`);\n\tconst endedAt = validateIsoLike(value.endedAt, `${label}.endedAt`);\n\tif (endedAt !== undefined && startedAt === undefined) throw new Error(`${label}.endedAt requires startedAt`);\n\tif (status === \"running\" && endedAt !== undefined) throw new Error(`${label}.running must not have endedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tpluginId: value.pluginId,\n\t\tkind: value.kind,\n\t\tstatus,\n\t\t...(inputArtifacts === undefined ? {} : { inputArtifacts }),\n\t\t...(outputArtifacts === undefined ? {} : { outputArtifacts }),\n\t\t...(startedAt === undefined ? {} : { startedAt }),\n\t\t...(endedAt === undefined ? {} : { endedAt }),\n\t});\n}\n\nexport function buildPluginOperationV1(input: PluginOperationV1): PluginOperationV1 {\n\treturn validatePluginOperationV1(input);\n}\n\nexport function validateEvidenceRefV1(value: unknown, label = \"Evidence reference\"): EvidenceRefV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"evidenceId\", \"kind\", \"operationId\"],\n\t\t[\"artifactRef\", \"summary\", \"observedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.evidenceId, `${label}.evidenceId`);\n\tconst kind = validateStatus(value.kind, `${label}.kind`, EVIDENCE_KINDS_V1) as EvidenceKindV1;\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst artifactRef =\n\t\tvalue.artifactRef === undefined ? undefined : validateArtifactRefV1(value.artifactRef, `${label}.artifactRef`);\n\tassertOptionalString(value.summary, `${label}.summary`);\n\tconst observedAt = validateIsoLike(value.observedAt, `${label}.observedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tevidenceId: value.evidenceId,\n\t\tkind,\n\t\toperationId: value.operationId,\n\t\t...(artifactRef === undefined ? {} : { artifactRef }),\n\t\t...(value.summary === undefined ? {} : { summary: value.summary }),\n\t\t...(observedAt === undefined ? {} : { observedAt }),\n\t});\n}\n\nexport function buildEvidenceRefV1(input: EvidenceRefV1): EvidenceRefV1 {\n\treturn validateEvidenceRefV1(input);\n}\n\nexport function validateVerificationV1(value: unknown, label = \"Verification\"): VerificationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"status\", \"checks\"], [], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tconst status = validateStatus(value.status, `${label}.status`, [\n\t\t\"passed\",\n\t\t\"failed\",\n\t\t\"unknown\",\n\t]) as VerificationV1[\"status\"];\n\tif (!Array.isArray(value.checks) || value.checks.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(`${label}.checks must contain non-empty strings`);\n\t}\n\tif (new Set(value.checks).size !== value.checks.length)\n\t\tthrow new Error(`${label}.checks must not contain duplicates`);\n\treturn detachedFreeze({ version: 1, status, checks: [...value.checks].sort() });\n}\n\nexport function buildVerificationV1(input: VerificationV1): VerificationV1 {\n\treturn validateVerificationV1(input);\n}\n\nexport function validatePluginErrorV1(value: unknown, label = \"Plugin error\"): PluginErrorV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"code\", \"message\"], [\"causeCode\", \"diagnosticRef\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.code, `${label}.code`);\n\tassertNonEmptyString(value.message, `${label}.message`);\n\tassertOptionalString(value.causeCode, `${label}.causeCode`);\n\tconst diagnosticRef =\n\t\tvalue.diagnosticRef === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactRefV1(value.diagnosticRef, `${label}.diagnosticRef`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tcode: value.code,\n\t\tmessage: value.message,\n\t\t...(value.causeCode === undefined ? {} : { causeCode: value.causeCode }),\n\t\t...(diagnosticRef === undefined ? {} : { diagnosticRef }),\n\t});\n}\n\nexport function buildPluginErrorV1(input: PluginErrorV1): PluginErrorV1 {\n\treturn validatePluginErrorV1(input);\n}\n\nexport function validatePluginResultV1(value: unknown, label = \"Plugin result\"): PluginResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"summary\", \"artifacts\", \"evidence\"],\n\t\t[\"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst status = validateStatus(value.status, `${label}.status`, RESULT_STATUSES_V1) as PluginResultStatusV1;\n\tassertNonEmptyString(value.summary, `${label}.summary`);\n\tconst artifacts = validateArtifactArray(value.artifacts, `${label}.artifacts`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tconst orderedEvidence = Object.freeze(sortByKey(evidence, (item) => item.evidenceId));\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (status !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${status}`);\n\tif (status === \"succeeded\" && error !== undefined) throw new Error(`${label}.succeeded must not include error`);\n\tif (status === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tstatus,\n\t\tsummary: value.summary,\n\t\tartifacts,\n\t\tevidence: orderedEvidence,\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t});\n}\n\nexport function buildPluginResultV1(input: PluginResultV1): PluginResultV1 {\n\treturn validatePluginResultV1(input);\n}\n\nexport function assertJsonResultInputV1(value: unknown, label = \"Result input\"): JsonValue {\n\tassertJsonValue(value, label);\n\treturn detachedFreeze(value);\n}\n"]}
1
+ {"version":3,"file":"operation.js","sourceRoot":"","sources":["../src/operation.ts"],"names":[],"mappings":"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","sourcesContent":["import { type ArtifactRefV1, validateArtifactRefV1 } from \"./artifact.ts\";\nimport {\n\tassertExactKeys,\n\tassertJsonValue,\n\tassertNonEmptyString,\n\tassertOptionalString,\n\tassertPlainRecord,\n\tdetachedFreeze,\n\ttype JsonValue,\n\tsortByKey,\n} from \"./common.ts\";\n\nexport type PluginOperationStatusV1 =\n\t| \"queued\"\n\t| \"running\"\n\t| \"succeeded\"\n\t| \"failed\"\n\t| \"cancelled\"\n\t| \"needs_user\"\n\t| \"unsupported\";\n\nexport const PLUGIN_OPERATION_TERMINAL_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\nexport interface PluginOperationV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly pluginId: string;\n\treadonly kind: string;\n\treadonly status: PluginOperationStatusV1;\n\treadonly inputArtifacts?: readonly ArtifactRefV1[];\n\treadonly outputArtifacts?: readonly ArtifactRefV1[];\n\treadonly startedAt?: string;\n\treadonly endedAt?: string;\n}\n\nexport type EvidenceKindV1 =\n\t| \"screenshot\"\n\t| \"dom\"\n\t| \"render\"\n\t| \"diff\"\n\t| \"log\"\n\t| \"assertion\"\n\t| \"trace\"\n\t| \"network\"\n\t| \"structure\";\n\nexport interface EvidenceRefV1 {\n\treadonly version: 1;\n\treadonly evidenceId: string;\n\treadonly kind: EvidenceKindV1;\n\treadonly operationId: string;\n\treadonly artifactRef?: ArtifactRefV1;\n\treadonly summary?: string;\n\treadonly observedAt?: string;\n}\n\nexport interface VerificationV1 {\n\treadonly version: 1;\n\treadonly status: \"passed\" | \"failed\" | \"unknown\";\n\treadonly checks: readonly string[];\n}\n\nexport interface PluginErrorV1 {\n\treadonly version: 1;\n\treadonly code: string;\n\treadonly message: string;\n\treadonly causeCode?: string;\n\treadonly diagnosticRef?: ArtifactRefV1;\n}\n\nexport type PluginResultStatusV1 = Exclude<PluginOperationStatusV1, \"queued\" | \"running\">;\n\n/**\n * Model-facing content blocks carried by a plugin result (2026-10-21 内容统一决策,\n * design §9.2). `content` is the only channel the model sees: images must ride\n * here as image blocks, never as base64 text inside `summary`/`evidence`.\n */\nexport interface PluginTextBlockV1 {\n\treadonly type: \"text\";\n\treadonly text: string;\n}\n\nexport interface PluginImageBlockV1 {\n\treadonly type: \"image\";\n\treadonly data: string;\n\treadonly mimeType: string;\n}\n\nexport type PluginContentBlockV1 = PluginTextBlockV1 | PluginImageBlockV1;\n\nexport interface PluginResultV1 {\n\treadonly version: 1;\n\treadonly operationId: string;\n\treadonly status: PluginResultStatusV1;\n\treadonly summary: string;\n\treadonly artifacts: readonly ArtifactRefV1[];\n\treadonly evidence: readonly EvidenceRefV1[];\n\treadonly content?: readonly PluginContentBlockV1[];\n\treadonly verification?: VerificationV1;\n\treadonly error?: PluginErrorV1;\n}\n\nconst EVIDENCE_KINDS_V1: readonly EvidenceKindV1[] = [\n\t\"screenshot\",\n\t\"dom\",\n\t\"render\",\n\t\"diff\",\n\t\"log\",\n\t\"assertion\",\n\t\"trace\",\n\t\"network\",\n\t\"structure\",\n];\nconst OPERATION_STATUSES_V1: readonly PluginOperationStatusV1[] = [\n\t\"queued\",\n\t\"running\",\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\nconst RESULT_STATUSES_V1: readonly PluginResultStatusV1[] = [\n\t\"succeeded\",\n\t\"failed\",\n\t\"cancelled\",\n\t\"needs_user\",\n\t\"unsupported\",\n];\n\n/**\n * Evidence summaries are bounded human-readable markers, not a payload channel\n * (2026-10-21 design §9.2). 128 KiB is twice the largest first-party evidence\n * preview (browser DOM 64 KiB), so screenshots or other base64 payloads cannot\n * ride here — they must go through `content` as image blocks.\n */\nexport const EVIDENCE_SUMMARY_MAX_CHARS_V1 = 128 * 1024;\n\n/**\n * `summary` is the one-line human-readable headline of a result, not a payload\n * channel; long output belongs in `content` text blocks or behind a reference.\n */\nexport const PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1 = 8 * 1024;\n\nfunction validateStatus(value: unknown, label: string, values: readonly string[]): string {\n\tif (typeof value !== \"string\" || !values.includes(value)) throw new Error(`${label} is invalid`);\n\treturn value;\n}\n\nfunction validateIsoLike(value: unknown, label: string): string | undefined {\n\tif (value === undefined) return undefined;\n\tassertNonEmptyString(value, label);\n\treturn value;\n}\n\nfunction validateArtifactArray(value: unknown, label: string): readonly ArtifactRefV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tconst result = value.map((item, index) => validateArtifactRefV1(item, `${label}[${index}]`));\n\tif (new Set(result.map((item) => item.artifactRef)).size !== result.length) {\n\t\tthrow new Error(`${label} must not contain duplicate artifactRef values`);\n\t}\n\treturn Object.freeze(sortByKey(result, (item) => item.artifactRef));\n}\n\nexport function validatePluginOperationV1(value: unknown, label = \"Plugin operation\"): PluginOperationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"pluginId\", \"kind\", \"status\"],\n\t\t[\"inputArtifacts\", \"outputArtifacts\", \"startedAt\", \"endedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tassertNonEmptyString(value.pluginId, `${label}.pluginId`);\n\tassertNonEmptyString(value.kind, `${label}.kind`);\n\tconst status = validateStatus(value.status, `${label}.status`, OPERATION_STATUSES_V1) as PluginOperationStatusV1;\n\tconst inputArtifacts =\n\t\tvalue.inputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.inputArtifacts, `${label}.inputArtifacts`);\n\tconst outputArtifacts =\n\t\tvalue.outputArtifacts === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactArray(value.outputArtifacts, `${label}.outputArtifacts`);\n\tconst startedAt = validateIsoLike(value.startedAt, `${label}.startedAt`);\n\tconst endedAt = validateIsoLike(value.endedAt, `${label}.endedAt`);\n\tif (endedAt !== undefined && startedAt === undefined) throw new Error(`${label}.endedAt requires startedAt`);\n\tif (status === \"running\" && endedAt !== undefined) throw new Error(`${label}.running must not have endedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tpluginId: value.pluginId,\n\t\tkind: value.kind,\n\t\tstatus,\n\t\t...(inputArtifacts === undefined ? {} : { inputArtifacts }),\n\t\t...(outputArtifacts === undefined ? {} : { outputArtifacts }),\n\t\t...(startedAt === undefined ? {} : { startedAt }),\n\t\t...(endedAt === undefined ? {} : { endedAt }),\n\t});\n}\n\nexport function buildPluginOperationV1(input: PluginOperationV1): PluginOperationV1 {\n\treturn validatePluginOperationV1(input);\n}\n\nexport function validateEvidenceRefV1(value: unknown, label = \"Evidence reference\"): EvidenceRefV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"evidenceId\", \"kind\", \"operationId\"],\n\t\t[\"artifactRef\", \"summary\", \"observedAt\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.evidenceId, `${label}.evidenceId`);\n\tconst kind = validateStatus(value.kind, `${label}.kind`, EVIDENCE_KINDS_V1) as EvidenceKindV1;\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst artifactRef =\n\t\tvalue.artifactRef === undefined ? undefined : validateArtifactRefV1(value.artifactRef, `${label}.artifactRef`);\n\tassertOptionalString(value.summary, `${label}.summary`);\n\tif (value.summary !== undefined && value.summary.length > EVIDENCE_SUMMARY_MAX_CHARS_V1) {\n\t\tthrow new Error(\n\t\t\t`${label}.summary must not exceed ${EVIDENCE_SUMMARY_MAX_CHARS_V1} characters; carry model-facing content in content blocks`,\n\t\t);\n\t}\n\tconst observedAt = validateIsoLike(value.observedAt, `${label}.observedAt`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tevidenceId: value.evidenceId,\n\t\tkind,\n\t\toperationId: value.operationId,\n\t\t...(artifactRef === undefined ? {} : { artifactRef }),\n\t\t...(value.summary === undefined ? {} : { summary: value.summary }),\n\t\t...(observedAt === undefined ? {} : { observedAt }),\n\t});\n}\n\nexport function buildEvidenceRefV1(input: EvidenceRefV1): EvidenceRefV1 {\n\treturn validateEvidenceRefV1(input);\n}\n\nexport function validateVerificationV1(value: unknown, label = \"Verification\"): VerificationV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"status\", \"checks\"], [], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tconst status = validateStatus(value.status, `${label}.status`, [\n\t\t\"passed\",\n\t\t\"failed\",\n\t\t\"unknown\",\n\t]) as VerificationV1[\"status\"];\n\tif (!Array.isArray(value.checks) || value.checks.some((item) => typeof item !== \"string\" || item.trim() === \"\")) {\n\t\tthrow new Error(`${label}.checks must contain non-empty strings`);\n\t}\n\tif (new Set(value.checks).size !== value.checks.length)\n\t\tthrow new Error(`${label}.checks must not contain duplicates`);\n\treturn detachedFreeze({ version: 1, status, checks: [...value.checks].sort() });\n}\n\nexport function buildVerificationV1(input: VerificationV1): VerificationV1 {\n\treturn validateVerificationV1(input);\n}\n\nexport function validatePluginErrorV1(value: unknown, label = \"Plugin error\"): PluginErrorV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(value, [\"version\", \"code\", \"message\"], [\"causeCode\", \"diagnosticRef\"], label);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.code, `${label}.code`);\n\tassertNonEmptyString(value.message, `${label}.message`);\n\tassertOptionalString(value.causeCode, `${label}.causeCode`);\n\tconst diagnosticRef =\n\t\tvalue.diagnosticRef === undefined\n\t\t\t? undefined\n\t\t\t: validateArtifactRefV1(value.diagnosticRef, `${label}.diagnosticRef`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\tcode: value.code,\n\t\tmessage: value.message,\n\t\t...(value.causeCode === undefined ? {} : { causeCode: value.causeCode }),\n\t\t...(diagnosticRef === undefined ? {} : { diagnosticRef }),\n\t});\n}\n\nexport function buildPluginErrorV1(input: PluginErrorV1): PluginErrorV1 {\n\treturn validatePluginErrorV1(input);\n}\n\n/** Upper bound on model-facing content blocks a single plugin result may carry. */\nexport const PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1 = 32;\n\nexport function validatePluginContentBlockV1(value: unknown, label = \"Plugin content block\"): PluginContentBlockV1 {\n\tassertPlainRecord(value, label);\n\tif (value.type === \"text\") {\n\t\tassertExactKeys(value, [\"type\", \"text\"], [], label);\n\t\tassertNonEmptyString(value.text, `${label}.text`);\n\t\treturn detachedFreeze({ type: \"text\" as const, text: value.text });\n\t}\n\tif (value.type === \"image\") {\n\t\tassertExactKeys(value, [\"type\", \"data\", \"mimeType\"], [], label);\n\t\tassertNonEmptyString(value.data, `${label}.data`);\n\t\tassertNonEmptyString(value.mimeType, `${label}.mimeType`);\n\t\tif (!value.mimeType.startsWith(\"image/\")) throw new Error(`${label}.mimeType must be an image media type`);\n\t\treturn detachedFreeze({ type: \"image\" as const, data: value.data, mimeType: value.mimeType });\n\t}\n\tthrow new Error(`${label}.type must be \"text\" or \"image\"`);\n}\n\nexport function validatePluginContentArray(value: unknown, label = \"Plugin content\"): readonly PluginContentBlockV1[] {\n\tif (!Array.isArray(value)) throw new Error(`${label} must be an array`);\n\tif (value.length > PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1)\n\t\tthrow new Error(`${label} must not exceed ${PLUGIN_RESULT_MAX_CONTENT_BLOCKS_V1} blocks`);\n\treturn Object.freeze(value.map((item, index) => validatePluginContentBlockV1(item, `${label}[${index}]`)));\n}\n\nexport function buildPluginContentBlockV1(input: PluginContentBlockV1): PluginContentBlockV1 {\n\treturn validatePluginContentBlockV1(input);\n}\n\nexport function validatePluginResultV1(value: unknown, label = \"Plugin result\"): PluginResultV1 {\n\tassertPlainRecord(value, label);\n\tassertExactKeys(\n\t\tvalue,\n\t\t[\"version\", \"operationId\", \"status\", \"summary\", \"artifacts\", \"evidence\"],\n\t\t[\"content\", \"verification\", \"error\"],\n\t\tlabel,\n\t);\n\tif (value.version !== 1) throw new Error(`${label}.version must be 1`);\n\tassertNonEmptyString(value.operationId, `${label}.operationId`);\n\tconst status = validateStatus(value.status, `${label}.status`, RESULT_STATUSES_V1) as PluginResultStatusV1;\n\tassertNonEmptyString(value.summary, `${label}.summary`);\n\tif (value.summary.length > PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1) {\n\t\tthrow new Error(\n\t\t\t`${label}.summary must not exceed ${PLUGIN_RESULT_SUMMARY_MAX_CHARS_V1} characters; put long output in content blocks`,\n\t\t);\n\t}\n\tconst artifacts = validateArtifactArray(value.artifacts, `${label}.artifacts`);\n\tif (!Array.isArray(value.evidence)) throw new Error(`${label}.evidence must be an array`);\n\tconst evidence = value.evidence.map((item, index) => validateEvidenceRefV1(item, `${label}.evidence[${index}]`));\n\tif (new Set(evidence.map((item) => item.evidenceId)).size !== evidence.length)\n\t\tthrow new Error(`${label}.evidence must not contain duplicate evidenceId values`);\n\tconst orderedEvidence = Object.freeze(sortByKey(evidence, (item) => item.evidenceId));\n\tconst content =\n\t\tvalue.content === undefined ? undefined : validatePluginContentArray(value.content, `${label}.content`);\n\tconst verification =\n\t\tvalue.verification === undefined\n\t\t\t? undefined\n\t\t\t: validateVerificationV1(value.verification, `${label}.verification`);\n\tconst error = value.error === undefined ? undefined : validatePluginErrorV1(value.error, `${label}.error`);\n\tif (status !== \"succeeded\" && error === undefined)\n\t\tthrow new Error(`${label}.error is required for status ${status}`);\n\tif (status === \"succeeded\" && error !== undefined) throw new Error(`${label}.succeeded must not include error`);\n\tif (status === \"succeeded\" && verification?.status === \"failed\")\n\t\tthrow new Error(`${label}.succeeded cannot have failed verification`);\n\treturn detachedFreeze({\n\t\tversion: 1,\n\t\toperationId: value.operationId,\n\t\tstatus,\n\t\tsummary: value.summary,\n\t\tartifacts,\n\t\tevidence: orderedEvidence,\n\t\t...(content === undefined ? {} : { content }),\n\t\t...(verification === undefined ? {} : { verification }),\n\t\t...(error === undefined ? {} : { error }),\n\t});\n}\n\nexport function buildPluginResultV1(input: PluginResultV1): PluginResultV1 {\n\treturn validatePluginResultV1(input);\n}\n\nexport function assertJsonResultInputV1(value: unknown, label = \"Result input\"): JsonValue {\n\tassertJsonValue(value, label);\n\treturn detachedFreeze(value);\n}\n"]}