@zhushanwen/pi-structured-output 6.2.3 → 6.2.4

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zhushanwen/pi-structured-output",
3
- "version": "6.2.3",
3
+ "version": "6.2.4",
4
4
  "description": "Structured output tool for Pi — workflow schema enforcement via pi's parameter layer; interactive mode validates self-reported schemas with Ajv",
5
5
  "type": "module",
6
6
  "main": "index.ts",
@@ -26,7 +26,7 @@
26
26
  ],
27
27
  "dependencies": {
28
28
  "ajv": "^8.17.0",
29
- "@zhushanwen/extension-protocol": "0.24.0",
29
+ "@zhushanwen/extension-protocol": "0.25.0",
30
30
  "@zhushanwen/pi-ext-guards": "0.5.0"
31
31
  },
32
32
  "peerDependencies": {
@@ -51,6 +51,6 @@
51
51
  },
52
52
  "scripts": {
53
53
  "typecheck": "npx tsc --noEmit",
54
- "test": "vitest run"
54
+ "test": "vitest run --pool=threads"
55
55
  }
56
56
  }
@@ -97,7 +97,7 @@ export function isTurnEndEvent(e: unknown): e is { message?: { stopReason?: stri
97
97
  /** tool_execution_end event 结构检查(替代直接 cast,配合 taste/no-unsafe-cast)。 */
98
98
  export function isToolExecutionEndEvent(
99
99
  e: unknown,
100
- ): e is { toolName: unknown; isError: unknown; result?: unknown } {
100
+ ): e is { toolName: unknown; isError: unknown; toolCallId?: unknown; result?: unknown } {
101
101
  return typeof e === "object" && e !== null && "toolName" in e && "isError" in e;
102
102
  }
103
103
 
@@ -107,4 +107,4 @@ export function isToolExecutionEndEvent(
107
107
  * 反向 import tool-definition 会成环。
108
108
  */
109
109
  export const CORRECT_USAGE_HINT =
110
- "Correct: structured-output({schema:{type:'object',properties:{...}}, data:{...actual values}}). ";
110
+ "Correct: structured_output({schema:{type:'object',properties:{...}}, data:{...actual values}}). ";
@@ -32,7 +32,12 @@ import {
32
32
  tryParseJson,
33
33
  } from "./schema-guards.js";
34
34
 
35
- export const TOOL_NAME = "structured-output";
35
+ // 工具名用下划线(项目工具命名规范):连字符名在 codemode 沙箱会被 toCodemodeIdentifier
36
+ // 转成下划线形态,同一工具对模型呈现两个名字,弱模型名字形态先验被下划线主导时
37
+ // 会直调连字符变体失败(2026-10-27 fanout 事故:deepseek-flash 直调 structured_output
38
+ // not found → 旁路 codemode 成功 → 父层采不到)。下划线名在 codemode 是恒等映射,
39
+ // 三个通道(LLM 工具表 / addendum / codemode API)名字一致。
40
+ export const TOOL_NAME = "structured_output";
36
41
  export const ENV_SCHEMA = "PI_WORKFLOW_SCHEMA";
37
42
 
38
43
  /**
@@ -37,7 +37,7 @@ type PiAPI = ExtensionAPI;
37
37
 
38
38
  // 与 tool-definition.ts 的 TOOL_NAME 对应(本模块只监听该工具的 execution 事件;
39
39
  // 依赖图单向:workflow-hook → schema-guards / loop-gate / text-primitives,不成环)。
40
- const TOOL_NAME = "structured-output";
40
+ const TOOL_NAME = "structured_output";
41
41
  const MAX_HOOK_RETRIES = 2;
42
42
 
43
43
  /** steer 发送失败告警的 appendEntry customType(session JSONL 持久化,不进 LLM 上下文)。 */
@@ -99,30 +99,33 @@ function buildSteerReminder(
99
99
  ): string {
100
100
  if (calledButFailed) {
101
101
  return [
102
- "[MANDATORY] Your structured-output call FAILED validation:",
103
- truncateText(lastSchemaError ?? "structured-output call failed", STEER_ERROR_MAX_CHARS),
102
+ "[MANDATORY] Your structured_output call FAILED validation:",
103
+ truncateText(lastSchemaError ?? "structured_output call failed", STEER_ERROR_MAX_CHARS),
104
104
  "",
105
105
  "The schema is enforced by the system (PI_WORKFLOW_SCHEMA) — this tool's parameter schema IS the required shape of your result.",
106
106
  `The required schema for your result is: ${schemaJson}`,
107
- "Fix your arguments to conform to this schema and call the structured-output tool AGAIN.",
107
+ "Fix your arguments to conform to this schema and call the structured_output tool AGAIN.",
108
+ "Call it directly as a top-level tool call (NOT via codemode / tools.xxx API) — only a direct tool call is recorded as your result.",
108
109
  "Do NOT output the result as text — call the tool.",
109
110
  ].join("\n");
110
111
  }
111
112
  return isObjectRoot
112
113
  ? [
113
- "[MANDATORY] You MUST call the structured-output tool now.",
114
+ "[MANDATORY] You MUST call the structured_output tool now.",
114
115
  "Your task requires a structured output. Do NOT respond with plain text.",
115
- `Your arguments ARE the data — call structured-output with your result as the tool's arguments, matching this shape: ${schemaJson}`,
116
+ `Your arguments ARE the data — call structured_output with your result as the tool's arguments, matching this shape: ${schemaJson}`,
116
117
  "The schema is enforced by the system; your arguments are validated against the authoritative schema automatically.",
118
+ "Call it directly as a top-level tool call (NOT via codemode / tools.xxx API) — only a direct tool call is recorded as your result.",
117
119
  "This is enforced by the workflow system. Just call the tool.",
118
120
  ].join("\n")
119
121
  : [
120
- "[MANDATORY] You MUST call the structured-output tool now.",
122
+ "[MANDATORY] You MUST call the structured_output tool now.",
121
123
  "Your task requires a structured output. Do NOT respond with plain text.",
122
- `Call structured-output with a single argument \`{value: <data>}\` — put the result itself in \`value\`, and it must conform to this shape: ${schemaJson}`,
124
+ `Call structured_output with a single argument \`{value: <data>}\` — put the result itself in \`value\`, and it must conform to this shape: ${schemaJson}`,
123
125
  "Non-object schemas are wrapped in a `value` field because tool call arguments must be objects.",
124
126
  "Validation errors may reference paths starting with `value.` (e.g. `value.0`, `value.name`): that prefix addresses the wrapper, not your data — strip it to locate the offending field.",
125
127
  "The schema is enforced by the system; the `value` field is validated against the authoritative schema automatically.",
128
+ "Call it directly as a top-level tool call (NOT via codemode / tools.xxx API) — only a direct tool call is recorded as your result.",
126
129
  "This is enforced by the workflow system. Just call the tool.",
127
130
  ].join("\n");
128
131
  }
@@ -181,6 +184,15 @@ export function setupWorkflowHook(pi: PiAPI, schemaJson: string): void {
181
184
  pi.on("tool_execution_end", async (event: unknown, ctx: ExtensionContext) => {
182
185
  if (!isToolExecutionEndEvent(event)) return;
183
186
  if (event.toolName !== TOOL_NAME) return;
187
+ // 嵌套调用(codemode 沙箱 tools.xxx API 经 ctx.executeTool 执行)不算模型的
188
+ // 直接产出:pi 不持久化嵌套结果,workflow 引擎只从顶层 toolCalls 采集
189
+ // parsedOutput——若照常记账,soSucceededEver 被置位 → turn_end 不再 steer →
190
+ // 父层判「never called」(2026-10-27 fanout 事故形态)。判据 = toolCallId 含
191
+ // "/":pi 嵌套管线 id 形态为 `${callerId}/${序号}`(nested-tool-calls.js),
192
+ // 顶层 id 由 provider 生成(call_xxx,无斜杠);事件转发层剥掉 parentToolCallId,
193
+ // toolCallId 是唯一稳定抓手。误判方向安全:provider id 含斜杠被误判成嵌套的
194
+ // 后果只是少一次 steer,不产生错误行为。
195
+ if (typeof event.toolCallId === "string" && event.toolCallId.includes("/")) return;
184
196
 
185
197
  // errorText 不在此处 fallback——合一状态机记账段/计数段对 undefined 同用
186
198
  // "structured-output call failed" 通用提示(与合一前两 listener 传入形态等价)。