// Copyright 2026 Alibaba Group // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package output import ( "bytes" "fmt" "io" "strings" "github.com/spf13/cobra" ) // WriteEnvelope 是统一信封的出口函数(B0 dev 试点桥接,WS1 改动点2/4)。 // 它复用 envelope.go 的权威 Envelope 类型,把信封按当前 --format 分发写出。 // Phase B 的 Emitter 类型将在此之上泛化(渲染先进 buffer、exit code 映射等); // 本函数仅提供 Phase F dev 域迁移所需的「信封 → cmd 流」出口,不重复定义类型。 // // format 分发(契约规范 §5.2,经 renderEnvelope 实现): // - json(默认):输出完整信封——唯一 JSON 契约(§5.2 首行)。 // - table/pretty:业务数据人读视图 + meta 摘要(不输出信封外壳)。 // - csv/ndjson:裸记录(无信封);meta.pagination 走 stderr 诊断行。 // - raw:数据载荷透传(与 Write 的 raw 语义一致)。 // // 数据出口一律写 cmd.OutOrStdout()、诊断出口写 cmd.ErrOrStderr()(均可被 // 测试重定向),不硬编码 os.Stdout/os.Stderr。未知 --format 值按 §5.2/AC-09 // 降级 fallback 并在此向 stderr 写一条 warning(不崩不静默)。 func WriteEnvelope(cmd *cobra.Command, env *Envelope, fallback Format) error { w, errW := io.Writer(io.Discard), io.Writer(io.Discard) format := fallback fields, jq, warning := "", "", "" if cmd != nil { w = cmd.OutOrStdout() errW = cmd.ErrOrStderr() format, warning = resolveFormatWithWarning(cmd, fallback) fields = ResolveFields(cmd) jq = ResolveJQ(cmd) } if env == nil { env = nilFallbackEnvelope() } if err := renderEnvelope(w, errW, env, format, fields, jq); err != nil { return err } // AC-09「不静默」:warning 仅在信封成功写出后补写 stderr—— // 写出失败时错误优先,不留半截通道。 if warning != "" { if _, werr := fmt.Fprintln(errW, "[WARN] "+warning); werr != nil { return werr } } return nil } // WriteEnvelopeTo 是 WriteEnvelope 的 writer 级实现(签名自 B205 保持不变), // 供不持有 *cobra.Command 的调用方与单测直接使用。B37 起收编到 renderEnvelope // 统一分发:json/空 format 输出完整信封(唯一 JSON 契约);table/pretty 输出 // 业务数据人读视图 + meta 摘要;csv/ndjson 输出裸记录(分页诊断行在本签名下 // 无 stderr 出口可走,按 io.Discard 处理——需要诊断行的调用方走 WriteEnvelope // 的 cmd 路径或 Emitter)。nil 信封降级为 I3 合法的 failure 兜底信封(B208): // 携带 internal 类 ErrorInfo——框架最后的兜底不得产出被自身 Validate() 拒绝的形态。 func WriteEnvelopeTo(w io.Writer, env *Envelope, format Format, fields, jq string) error { if env == nil { env = nilFallbackEnvelope() } return renderEnvelope(w, io.Discard, env, format, fields, jq) } // EmitResult is the framework 2.0 CLI adapter. Unlike the legacy Emitter, // machine-readable JSON/NDJSON always writes the primary result to stdout for // every outcome; stderr is diagnostics-only. Human failure formats retain the // conventional stderr path. The result is validated before any byte is // rendered, and the returned exit code comes from the same immutable result. func EmitResult(cmd *cobra.Command, result CommandResult) (int, error) { code, _, err := emitResult(cmd, result) return code, err } func emitResult(cmd *cobra.Command, result CommandResult) (int, bool, error) { if err := ValidateResult(result); err != nil { return exitCodeInternal, false, err } env := result.envelope() env = redactEnvelope(env) format, warning := resolveFormatWithWarning(cmd, FormatJSON) fields, jq := ResolveFields(cmd), ResolveJQ(cmd) stdout, stderr := io.Writer(io.Discard), io.Writer(io.Discard) if cmd != nil { stdout, stderr = cmd.OutOrStdout(), cmd.ErrOrStderr() } if env.Outcome == OutcomeFailure && format != FormatJSON && format != FormatNDJSON { message := "command failed" if env.Error != nil && strings.TrimSpace(env.Error.Message) != "" { message = env.Error.Message } var buf bytes.Buffer _, _ = fmt.Fprintln(&buf, "Error: "+message) n, err := writeAllCount(stderr, buf.Bytes()) if err != nil { return exitCodeInternal, n > 0, err } return result.ExitCode(), n > 0, nil } // Failure bypasses jq/fields so a filter cannot erase the typed error. if env.Outcome == OutcomeFailure { fields, jq = "", "" } var buf bytes.Buffer if err := renderEnvelope(&buf, stderr, env, format, fields, jq); err != nil { return exitCodeInternal, false, err } n, err := writeAllCount(stdout, buf.Bytes()) if err != nil { return exitCodeInternal, n > 0, err } if warning != "" { // The primary result is already complete. Diagnostics are best-effort // and must not reverse its outcome when stderr is unavailable. _, _ = fmt.Fprintln(stderr, "[WARN] "+warning) } return result.ExitCode(), n > 0, nil } // nilFallbackEnvelope 构造 nil 信封的兜底信封(B208,轮6裁决⑨)。保留 // 「不 panic、必产合法 failure」的既有语义,且自身满足不变量 I3 // (error 非空 ⇔ outcome==failure)、通过 Envelope.Validate()——框架出口 // 兜底不得被自己的校验器判非法。Type 取 "internal":这是框架内部缺陷而非 // 业务错误(§4 internal=5),Agent 可据 wire-stable 字段分支处置。 func nilFallbackEnvelope() *Envelope { return &Envelope{ OK: false, Outcome: OutcomeFailure, Error: &ErrorInfo{ Type: "internal", Message: "nil envelope: framework fallback", }, } } // --------------------------------------------------------------------------- // format 分发(B35~B40):信封 → format 矩阵的统一分发点 // (契约规范 §5.2;适用域仅 stdout 数据通道——轮8裁决⑪) // --------------------------------------------------------------------------- // renderEnvelope 是信封渲染的统一分发点(B37,WS1 改动点4;契约规范 §5.2 // format 矩阵)。渲染先进 buffer 再一次性写出:数据通道任何一步失败都不向 // w 泄漏半截输出(§5.2 末条;与 B25 buffer-first 同口径)。 // // 矩阵各行(适用域仅 stdout 数据通道——失败信封在 Emitter 流路由层恒以 // 完整 JSON 写 stderr,从不进入本矩阵,轮8裁决⑪): // // - json / 空:完整信封——唯一 JSON 契约(§5.2 首行,B38); // - table / pretty:业务数据人读视图 + meta 层人读摘要(count/pagination), // 不输出信封外壳(§5.2 矩阵行,B39); // - csv / ndjson:裸记录(无信封外壳);meta.pagination 走 errW 的 stderr // 诊断行,不污染裸记录机器可读流(§5.2 矩阵行,B40); // - raw:数据载荷透传(writeRaw 语义)。 // // --jq 优先于 format:对**完整信封**求值后直接输出结果(§5.2 矩阵行,B42)。 // --fields 先投影数据载荷再按 format 分发(成功通道联动)。errW 为 nil 时按 // io.Discard 处理(csv/ndjson 诊断行无出口即丢弃)。 // // buffer-first 纪律:全部分发路径(含 json/jq)先渲染进内存 buffer,成功后 // 才一次性写出到 w(§5.2 末条对分发点整体成立)——渲染失败零泄漏、写出 // 失败原子中止、短写报 io.ErrShortWrite。csv/ndjson 的 stderr 诊断行不经 // buffer,且在数据渲染成功后才写(渲染失败不留诊断噪声)。 func renderEnvelope(w io.Writer, errW io.Writer, env *Envelope, format Format, fields, jq string) error { if errW == nil { errW = io.Discard } if env == nil { env = nilFallbackEnvelope() } env = redactEnvelope(env) var buf bytes.Buffer if err := renderEnvelopeInto(&buf, errW, env, format, fields, jq); err != nil { return err } return writeAll(w, buf.Bytes()) } // renderEnvelopeInto 按 format 矩阵把信封渲染进 buf(renderEnvelope 的渲染段)。 func renderEnvelopeInto(buf *bytes.Buffer, errW io.Writer, env *Envelope, format Format, fields, jq string) error { // 失败信封不参与 format/fields/jq 分发(轮8裁决⑪;契约规范 §5.1 v1.1 // 回写条款):恒输出完整 JSON 信封。§5.2 矩阵适用域仅 stdout 数据通道, // 失败信封无 data 载荷(I3),若按矩阵分发或经 jq/fields 过滤,§2.4 // wire-stable 字段组将从错误通道消失。 if env.Outcome == OutcomeFailure { if format == FormatNDJSON { return Write(buf, FormatNDJSON, env) } return WriteFiltered(buf, FormatJSON, env, "", "") } if strings.TrimSpace(jq) != "" { // jq 优先于 format:对完整信封求值后直接输出(§5.2 矩阵行)。 return ApplyJQ(buf, env, strings.TrimSpace(jq)) } switch format { case FormatJSON, "": // json(默认)始终保留完整信封。--fields 只投影业务载荷, // ok/outcome/meta 等稳定契约字段仍必须可供 Agent 分支和续页。 if trimmed := strings.TrimSpace(fields); trimmed != "" { projected := *env projected.Data = SelectFields(env.Data, strings.Split(trimmed, ",")) return WriteFiltered(buf, FormatJSON, &projected, "", "") } return WriteFiltered(buf, FormatJSON, env, "", "") default: } payload := env.Data if trimmed := strings.TrimSpace(fields); trimmed != "" { payload = SelectFields(payload, strings.Split(trimmed, ",")) } switch format { case FormatTable, FormatPretty: if err := Write(buf, format, payload); err != nil { return err } if summary := renderMetaSummary(env.Meta); summary != "" { _, _ = fmt.Fprintf(buf, "\n%s\n", summary) } return nil case FormatCSV, FormatNDJSON: if err := Write(buf, format, payload); err != nil { return err } // 分页元数据走 stderr 诊断行(§5.2 csv/ndjson 行)。partial_failure // 例外:§2.3 明确「stderr 零输出」,特例优先于 §5.2 诊断行通则。 if env.Outcome != OutcomePartialFailure { return writePaginationDiagnostic(errW, env.Meta) } return nil case FormatRaw: return Write(buf, FormatRaw, payload) default: // 未知 Format 常量(归一化/ParseFormat 后理论不可达):按 json // 数据通道语义兜底,绝不静默丢弃载荷。--fields 同样投影业务载荷。 if trimmed := strings.TrimSpace(fields); trimmed != "" { return WriteFiltered(buf, FormatJSON, env.Data, trimmed, "") } return WriteFiltered(buf, FormatJSON, env, "", "") } } // writeAll 把 p 一次性写入 w;短写(接受了部分字节却未写全)报 // io.ErrShortWrite——与 Emitter.writeOnce 同一短写纪律,绝不把截断输出当成功。 func writeAll(w io.Writer, p []byte) error { _, err := writeAllCount(w, p) return err } func writeAllCount(w io.Writer, p []byte) (int, error) { n, err := w.Write(p) if err != nil { return n, err } if n < len(p) { return n, io.ErrShortWrite } return n, nil } // renderMetaSummary 把信封 meta 层渲染为人读摘要行(§5.2:table/pretty 输出 // 「业务数据 + 人读分页摘要」)。无 meta 内容返回空串。count 单独成行; // pagination 摘要复用 renderPaginationSummary。 func renderMetaSummary(meta *Meta) string { if meta == nil { return "" } var lines []string if meta.Count != nil { lines = append(lines, fmt.Sprintf("count: %d", *meta.Count)) } if meta.Pagination != nil { lines = append(lines, "pagination: "+renderPaginationSummary(meta.Pagination)) } return strings.Join(lines, "\n") } // renderPaginationSummary 按 wire 字段名输出分页摘要: // endpoint_exhausted/pages/items/next_token(snake_case 与 wire 一致,Agent // 可交叉核对;§3 两态显式语义保留——false 也输出,因为它是「可续跑」的 // 有意义状态)。 func renderPaginationSummary(p *Pagination) string { parts := []string{fmt.Sprintf("endpoint_exhausted: %t", p.EndpointExhausted)} if p.Pages > 0 { parts = append(parts, fmt.Sprintf("pages: %d", p.Pages)) } if p.Items > 0 { parts = append(parts, fmt.Sprintf("items: %d", p.Items)) } if p.NextToken != "" { parts = append(parts, fmt.Sprintf("next_token: %s", p.NextToken)) } return strings.Join(parts, ", ") } // writePaginationDiagnostic 把分页诊断行写到 errW(§5.2:csv/ndjson 裸记录 // 格式下分页元数据走 stderr 诊断行)。无 meta/pagination 时零输出—— // 不给无分页场景制造噪声。 func writePaginationDiagnostic(errW io.Writer, meta *Meta) error { if meta == nil || meta.Pagination == nil { return nil } _, err := fmt.Fprintln(errW, "[pagination] "+renderPaginationSummary(meta.Pagination)) return err } // --------------------------------------------------------------------------- // Phase B 泛化(B24~B29):Emitter 类型——success/pending/partial 走 stdout、 // failure 走 stderr 的双向出口;渲染先进 buffer 再一次性写出。 // --------------------------------------------------------------------------- // Emitter 是统一信封的双向出口(B24,WS1 改动点2)。它持有 stdout/stderr 两个 // 写入目标与当前 format/fields/jq 配置,按契约规范 §5.1 流向纪律把信封分发到 // 正确的流: // // - success / pending / partial_failure → stdout(数据通道); // - failure(含 nil 信封降级)→ stderr,stdout 严格零字节(AC-11)。 // // 每次写出都经过「先渲染进内存 buffer、再一次性写出」(B25,契约规范 §5.2 // 末条),写一半失败不产生残缺 JSON。渲染复用 WriteEnvelopeTo(B205), // Emitter 不重新定义信封类型或 wire 键。 type Emitter struct { stdout io.Writer stderr io.Writer format Format fields string jq string } // NewEmitter 构造 Emitter(B24)。nil writer 按 io.Discard 处理(等价丢弃, // 防 panic)。format 是解析后的当前 format(空值由 WriteEnvelopeTo 按 json // 处理);fields/jq 为命令解析出的 --fields/--jq 过滤条件。 func NewEmitter(stdout, stderr io.Writer, format Format, fields, jq string) *Emitter { if stdout == nil { stdout = io.Discard } if stderr == nil { stderr = io.Discard } return &Emitter{stdout: stdout, stderr: stderr, format: format, fields: fields, jq: jq} } // Emit 按 env 的 outcome 把信封写到对应流(B26/B27/B28,契约规范 §5.1): // // - success / pending:完整信封落 stdout(B26,ok:true,§2.1/§2.2); // - partial_failure:三通道明细落 stdout、stderr 零输出(B28,§2.3); // - failure:信封落 stderr、stdout 严格零字节(B27,AC-11); // - nil 信封由 WriteEnvelopeTo 降级为 failure,同样落 stderr。 // // exit 0 语义标注(B32 收口,契约规范 §4/I2):Emit 的流路由与 // ExitCodeForEnvelope 的退出码语义一一对应——落 stdout 的信封 // (success/pending/partial_failure 中 ok:true 的前两者)对应 exit 0, // 落 stderr 的 failure(含 nil 降级)对应非零退出码。Emitter 只负责写出, // 不自行 os.Exit;进程退出码由 runner 出口按同一映射统一决定(§4 规则)。 // // 失败信封恒以完整 JSON 信封渲染并绕过 format/fields/jq:失败信封没有数据 // 载荷(I3),§5.1 要求 stderr 上的失败信封恒为结构化信封;「非 json 只渲染 // data」的分发规则只适用于携带数据的信封。若 --jq 作用于失败信封会把错误 // 通道过滤为空,Agent 将丢失错误明细,故同样绕过。 func (em *Emitter) Emit(env *Envelope) error { if env == nil || env.Outcome == OutcomeFailure { return em.writeOnce(em.stderr, env, FormatJSON, "", "") } return em.writeOnce(em.stdout, env, em.format, em.fields, em.jq) } // writeOnce 先把信封完整渲染进内存 buffer,再一次性写出到 target(B25, // 契约规范 §5.2 末条「渲染先进 buffer 再一次性写出」):渲染失败不向 target // 泄漏任何字节;写出失败原子中止——writer 报错即原样返回,短写(接受了部分 // 字节却未写全)报 io.ErrShortWrite,绝不把截断输出当成功。渲染统一走 // renderEnvelope 分发点(B37),csv/ndjson 的分页诊断行(B40)以 em.stderr // 为诊断出口。 func (em *Emitter) writeOnce(target io.Writer, env *Envelope, format Format, fields, jq string) error { if env == nil { env = nilFallbackEnvelope() } var buf bytes.Buffer if err := renderEnvelope(&buf, em.stderr, env, format, fields, jq); err != nil { return err } p := buf.Bytes() n, err := target.Write(p) if err != nil { return err } if n < len(p) { return io.ErrShortWrite } return nil } // ParseFormat 把字符串形式的 format 值(来自 --format flag 或 caller 接口) // 归一化为 Format 常量;大小写不敏感,未知/空值返回 fallback。它是不持有 // *cobra.Command 的调用方(如主漏斗 callMCPToolInternalOpts)的 ResolveFormat // 等价物,二者共用同一归一化规则。 func ParseFormat(raw string, fallback Format) Format { return normalizeFormat(raw, fallback) } // --------------------------------------------------------------------------- // 退出码映射(B32/B33/B34):outcome / error 类别 → exit code // (契约规范 §4;不变量 I2 —— exit code == 0 ⇔ ok == true) // --------------------------------------------------------------------------- // 退出码表取值对齐:契约规范 §4 × 规划 v1.2 决策(OQ-1 已关闭——保留现行 // 码表 api=1 / auth=2 / validation=3 / PAT=4 / internal=5 / discovery=6, // 仅新增 partial_failure 专用码 7;草案表中 validation→2 / confirmation→3 // 的拆分属 wire 破坏性重排,放弃)。confirmation_required 是 failure 的子类, // 与 validation 共享 rc=3、以 subtype 区分(AC-13;runtime 同源于 // internal/errors 的 ExitCode 表)。 const ( exitCodeOK = 0 exitCodeAPI = 1 exitCodeAuth = 2 exitCodeValidation = 3 // confirmation_required 子类共享此码(OQ-1 定案) exitCodePermission = 4 // PAT / permission failures exitCodeInternal = 5 exitCodeDiscovery = 6 exitCodePartial = 7 // partial_failure 专用(契约 §4;规划 WS2 第4项;B142 将在 errors 侧补同源常量) ) // subtypeConfirmationRequired 是门禁拦截的 failure 子类标记(契约规范 §2.4), // 与 runtime apperrors.WithReason("confirmation_required") 形态同源。 const subtypeConfirmationRequired = "confirmation_required" // ExitCodeForEnvelope 计算信封对应的进程退出码(B33,AC-10/I2;契约规范 §4)。 // 纯函数:只读信封字段、不做任何 I/O。映射规则: // // - success / pending → 0(I2:exit 0 ⇔ ok;异步受理不是失败,§1/§4); // - partial_failure → 7(部分成功专用码,区别于普通失败,§4); // - failure → 按 Error.Type 类别映射,confirmation_required 子类恒 3; // - nil 信封 → 5(与 WriteEnvelopeTo 的 internal 类兜底信封同源); // - 非法 outcome → 5(非法信封属框架缺陷,归 internal)。 // // 判定只看 outcome / error 类别,不看可能被命令层篡改的 OK 字段(与 IsOK // 同口径,I1)。退出码由 runner 出口统一决定、命令层禁止自行 os.Exit // (§4 规则);本函数为 Emitter 语义标注、runner 映射与测试提供同一依据。 func ExitCodeForEnvelope(env *Envelope) int { if env == nil { return exitCodeInternal } switch env.Outcome { case OutcomeSuccess, OutcomePending: return exitCodeOK case OutcomePartialFailure: return exitCodePartial case OutcomeFailure: return exitCodeForErrorInfo(env.Error) default: return exitCodeInternal } } // exitCodeForErrorInfo 把失败信封的 error 类别映射为退出码(契约规范 §4)。 // subtype=confirmation_required 优先于 type:门禁拦截无论挂在哪个 type 下 // 都恒为 3(AC-13)。error 缺席(违反 I3 的形态)按 internal 处理—— // 没有明细可分支的失败不得假装可恢复。未知 type 归 internal(不应发生)。 func exitCodeForErrorInfo(info *ErrorInfo) int { if info == nil { return exitCodeInternal } if info.Subtype == subtypeConfirmationRequired { return exitCodeValidation } switch info.Type { case "api": return exitCodeAPI case "auth": return exitCodeAuth case "validation": return exitCodeValidation case "permission": return exitCodePermission case "discovery": return exitCodeDiscovery default: return exitCodeInternal } }