@lumenkey/cli 0.3.0 → 0.4.0-rc.1

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.
Files changed (184) hide show
  1. package/README.md +51 -2
  2. package/dist/cli/copied.js +86 -85
  3. package/dist/cli/index.js +56 -44
  4. package/dist/cli/render.js +5 -1
  5. package/dist/core/asset-stream.d.ts +16 -0
  6. package/dist/core/asset-stream.js +64 -0
  7. package/dist/core/atomic-config-file.js +8 -7
  8. package/dist/core/chinese-messages.d.ts +4 -0
  9. package/dist/core/chinese-messages.js +907 -0
  10. package/dist/core/chinese-script.d.ts +7 -0
  11. package/dist/core/chinese-script.js +28 -0
  12. package/dist/core/client.js +2 -1
  13. package/dist/core/config-dir.js +2 -1
  14. package/dist/core/config.js +7 -6
  15. package/dist/core/download-root.d.ts +5 -0
  16. package/dist/core/download-root.js +64 -0
  17. package/dist/core/download.js +15 -12
  18. package/dist/core/generation-input.js +3 -2
  19. package/dist/core/generation.d.ts +8 -1
  20. package/dist/core/generation.js +13 -10
  21. package/dist/core/job-display.js +7 -6
  22. package/dist/core/locale.d.ts +2 -0
  23. package/dist/core/locale.js +172 -142
  24. package/dist/core/outcome.d.ts +6 -0
  25. package/dist/core/outcome.js +17 -4
  26. package/dist/core/output-path.d.ts +3 -1
  27. package/dist/core/output-path.js +5 -2
  28. package/dist/core/params.js +26 -25
  29. package/dist/core/profiles.js +5 -4
  30. package/dist/core/set-values.d.ts +0 -1
  31. package/dist/core/set-values.js +7 -7
  32. package/dist/core/submission-journal.js +43 -17
  33. package/dist/core/version.js +2 -3
  34. package/dist/mcp/server.js +30 -29
  35. package/dist/studio/asset-integrity-hash.d.ts +28 -0
  36. package/dist/studio/asset-integrity-hash.js +93 -0
  37. package/dist/studio/asset-integrity-queue.d.ts +24 -0
  38. package/dist/studio/asset-integrity-queue.js +94 -0
  39. package/dist/studio/asset-store.d.ts +52 -0
  40. package/dist/studio/asset-store.js +352 -0
  41. package/dist/studio/canvas/LICENSES.txt +1590 -0
  42. package/dist/studio/canvas/dependencies.json +862 -0
  43. package/dist/studio/canvas/director-source-manifest.json +47 -0
  44. package/dist/studio/canvas/editor.css +239 -0
  45. package/dist/studio/canvas/editor.js +4613 -0
  46. package/dist/studio/canvas/integration-source-manifest.json +384 -0
  47. package/dist/studio/canvas/source-manifest.json +240 -0
  48. package/dist/studio/canvas-store.d.ts +1254 -0
  49. package/dist/studio/canvas-store.js +205 -0
  50. package/dist/studio/capture-placement.d.ts +11 -0
  51. package/dist/studio/capture-placement.js +85 -0
  52. package/dist/studio/cloud-contract.d.ts +128 -0
  53. package/dist/studio/cloud-contract.js +229 -0
  54. package/dist/studio/cloud-service.d.ts +178 -0
  55. package/dist/studio/cloud-service.js +202 -0
  56. package/dist/studio/director-captures.d.ts +59 -0
  57. package/dist/studio/director-captures.js +141 -0
  58. package/dist/studio/director-contract.d.ts +1489 -0
  59. package/dist/studio/director-contract.js +288 -0
  60. package/dist/studio/director-png.d.ts +6 -0
  61. package/dist/studio/director-png.js +41 -0
  62. package/dist/studio/director-relations.d.ts +40 -0
  63. package/dist/studio/director-relations.js +103 -0
  64. package/dist/studio/document-authority.d.ts +7 -0
  65. package/dist/studio/document-authority.js +50 -0
  66. package/dist/studio/draft-store.d.ts +95 -0
  67. package/dist/studio/draft-store.js +100 -0
  68. package/dist/studio/errors.d.ts +85 -0
  69. package/dist/studio/errors.js +94 -0
  70. package/dist/studio/launch.d.ts +5 -0
  71. package/dist/studio/launch.js +31 -0
  72. package/dist/studio/media.d.ts +9 -0
  73. package/dist/studio/media.js +50 -0
  74. package/dist/studio/output-save-contract.d.ts +1321 -0
  75. package/dist/studio/output-save-contract.js +26 -0
  76. package/dist/studio/output-save-journal.d.ts +971 -0
  77. package/dist/studio/output-save-journal.js +287 -0
  78. package/dist/studio/output-save-recovery.d.ts +18 -0
  79. package/dist/studio/output-save-recovery.js +161 -0
  80. package/dist/studio/output-save-service.d.ts +436 -0
  81. package/dist/studio/output-save-service.js +251 -0
  82. package/dist/studio/output-save-worker.d.ts +40 -0
  83. package/dist/studio/output-save-worker.js +360 -0
  84. package/dist/studio/paths.d.ts +6 -0
  85. package/dist/studio/paths.js +68 -0
  86. package/dist/studio/run-journal.d.ts +993 -0
  87. package/dist/studio/run-journal.js +125 -0
  88. package/dist/studio/server.d.ts +17 -0
  89. package/dist/studio/server.js +440 -0
  90. package/dist/studio/session.d.ts +6 -0
  91. package/dist/studio/session.js +26 -0
  92. package/dist/studio/shell.d.ts +3 -0
  93. package/dist/studio/shell.js +23 -0
  94. package/dist/studio/style.d.ts +2 -0
  95. package/dist/studio/style.js +29 -0
  96. package/dist/studio/types.d.ts +26 -0
  97. package/dist/studio/types.js +2 -0
  98. package/dist/studio/web/app.d.ts +2 -0
  99. package/dist/studio/web/app.js +662 -0
  100. package/dist/studio/web/asset-controller.d.ts +37 -0
  101. package/dist/studio/web/asset-controller.js +191 -0
  102. package/dist/studio/web/asset-model.d.ts +29 -0
  103. package/dist/studio/web/asset-model.js +51 -0
  104. package/dist/studio/web/controller.d.ts +113 -0
  105. package/dist/studio/web/controller.js +401 -0
  106. package/dist/studio/web/messages.d.ts +2 -0
  107. package/dist/studio/web/messages.js +74 -0
  108. package/dist/studio/web/model.d.ts +75 -0
  109. package/dist/studio/web/model.js +182 -0
  110. package/dist/studio/web/save-controller.d.ts +85 -0
  111. package/dist/studio/web/save-controller.js +495 -0
  112. package/dist/studio/web/save-model.d.ts +63 -0
  113. package/dist/studio/web/save-model.js +64 -0
  114. package/dist/studio/web/save-queue.d.ts +7 -0
  115. package/dist/studio/web/save-queue.js +62 -0
  116. package/dist/studio/web/save-view.d.ts +9 -0
  117. package/dist/studio/web/save-view.js +19 -0
  118. package/dist/studio/writer-lock.d.ts +5 -0
  119. package/dist/studio/writer-lock.js +88 -0
  120. package/package.json +11 -5
  121. package/dist/cli/copied.d.ts.map +0 -1
  122. package/dist/cli/copied.js.map +0 -1
  123. package/dist/cli/index.d.ts.map +0 -1
  124. package/dist/cli/index.js.map +0 -1
  125. package/dist/cli/render.d.ts.map +0 -1
  126. package/dist/cli/render.js.map +0 -1
  127. package/dist/core/atomic-config-file.d.ts.map +0 -1
  128. package/dist/core/atomic-config-file.js.map +0 -1
  129. package/dist/core/chat.d.ts.map +0 -1
  130. package/dist/core/chat.js.map +0 -1
  131. package/dist/core/client.d.ts.map +0 -1
  132. package/dist/core/client.js.map +0 -1
  133. package/dist/core/config-dir.d.ts.map +0 -1
  134. package/dist/core/config-dir.js.map +0 -1
  135. package/dist/core/config-error.d.ts.map +0 -1
  136. package/dist/core/config-error.js.map +0 -1
  137. package/dist/core/config.d.ts.map +0 -1
  138. package/dist/core/config.js.map +0 -1
  139. package/dist/core/credit-cap.d.ts.map +0 -1
  140. package/dist/core/credit-cap.js.map +0 -1
  141. package/dist/core/download.d.ts.map +0 -1
  142. package/dist/core/download.js.map +0 -1
  143. package/dist/core/error-result.d.ts.map +0 -1
  144. package/dist/core/error-result.js.map +0 -1
  145. package/dist/core/explain.d.ts.map +0 -1
  146. package/dist/core/explain.js.map +0 -1
  147. package/dist/core/generation-input.d.ts.map +0 -1
  148. package/dist/core/generation-input.js.map +0 -1
  149. package/dist/core/generation-result.d.ts.map +0 -1
  150. package/dist/core/generation-result.js.map +0 -1
  151. package/dist/core/generation.d.ts.map +0 -1
  152. package/dist/core/generation.js.map +0 -1
  153. package/dist/core/job-display.d.ts.map +0 -1
  154. package/dist/core/job-display.js.map +0 -1
  155. package/dist/core/locale.d.ts.map +0 -1
  156. package/dist/core/locale.js.map +0 -1
  157. package/dist/core/origin.d.ts.map +0 -1
  158. package/dist/core/origin.js.map +0 -1
  159. package/dist/core/outcome.d.ts.map +0 -1
  160. package/dist/core/outcome.js.map +0 -1
  161. package/dist/core/output-path.d.ts.map +0 -1
  162. package/dist/core/output-path.js.map +0 -1
  163. package/dist/core/params.d.ts.map +0 -1
  164. package/dist/core/params.js.map +0 -1
  165. package/dist/core/poller.d.ts.map +0 -1
  166. package/dist/core/poller.js.map +0 -1
  167. package/dist/core/profiles.d.ts.map +0 -1
  168. package/dist/core/profiles.js.map +0 -1
  169. package/dist/core/set-values.d.ts.map +0 -1
  170. package/dist/core/set-values.js.map +0 -1
  171. package/dist/core/submission-journal.d.ts.map +0 -1
  172. package/dist/core/submission-journal.js.map +0 -1
  173. package/dist/core/types.d.ts.map +0 -1
  174. package/dist/core/types.js.map +0 -1
  175. package/dist/core/version.d.ts.map +0 -1
  176. package/dist/core/version.js.map +0 -1
  177. package/dist/core/wait.d.ts.map +0 -1
  178. package/dist/core/wait.js.map +0 -1
  179. package/dist/index.d.ts.map +0 -1
  180. package/dist/index.js.map +0 -1
  181. package/dist/mcp/render.d.ts.map +0 -1
  182. package/dist/mcp/render.js.map +0 -1
  183. package/dist/mcp/server.d.ts.map +0 -1
  184. package/dist/mcp/server.js.map +0 -1
@@ -1,4 +1,8 @@
1
+ import { zhText } from '../core/chinese-messages.js';
1
2
  const EN = {
3
+ DOWNLOAD_ROOT_INVALID: 'LUMENKEY_DOWNLOAD_ROOT must be an absolute path.',
4
+ DOWNLOAD_OUTSIDE_ROOT: 'The download target is outside the allowed root or contains parent traversal.',
5
+ DOWNLOAD_UNSAFE_PATH: 'The download path contains an unsafe link or cannot be verified.',
2
6
  API_KEY_NOT_CONFIGURED: 'key: not configured. Set LUMENKEY_API_KEY or run lumenkey login.',
3
7
  INVALID_API_KEY: 'This key was rejected. Check LUMENKEY_API_KEY or run lumenkey login.',
4
8
  MODEL_REQUIRED: 'Choose a model with -m <modelId>.',
@@ -106,111 +110,114 @@ const EN = {
106
110
  CLI_COMMAND_FAILED: 'The command failed before it could finish.',
107
111
  };
108
112
  const ZH = {
109
- API_KEY_NOT_CONFIGURED: '尚未配置密钥。请设置 LUMENKEY_API_KEY,或运行 lumenkey login。',
110
- INVALID_API_KEY: '密钥无效或已失效,请确认后重试。',
111
- MODEL_REQUIRED: '请用 -m <模型ID> 指定模型。',
112
- MODEL_NOT_IN_CATALOG: '该模型 ID 不在你的可用目录中。运行 lumenkey models 查看准确 ID。',
113
- BASE_URL_INVALID: '网关地址必须是 HTTP(S) 源地址,且不能包含账号密码、查询参数或片段。',
114
- TRANSPORT_FAILED: '无法连接网关,请检查网络与网关地址。',
115
- PROXY_URL_INVALID: '代理地址无效,请检查代理环境变量。',
116
- PROXY_PROTOCOL_UNSUPPORTED: '仅支持 HTTP(S) 代理;未绕过已配置的代理直连。',
117
- CLI_ARGUMENTS_INVALID: '命令包含多余的位置参数,请用 --help 检查用法。',
118
- HTTP_ERROR: '网关无法完成本次请求。',
119
- API_REQUEST_FAILED: '网关无法完成本次请求。',
120
- JOB_ID_INVALID: '任务 ID 不能为空,且必须是 UUID 格式。',
121
- JOB_NOT_FOUND: '未找到该任务。注意:报价编号不能用于查询任务。',
122
- CHAT_CREDIT_LIMIT_INVALID: '对话花费上限必须是非负整数。',
123
- CHAT_CREDIT_LIMIT_EXCEEDED: '本次对话所需预授权超过你的花费上限,已阻止。未发起对话,也未冻结算力点。',
124
- STREAM_SETTLEMENT_PENDING: '对话失败,但结算尚未确认。算力点可能仍处于冻结状态,请不要认为已自动归还。',
125
- STREAM_INPUT_SETTLEMENT_PENDING: '先前请求的结算尚未确认,算力点可能仍处于冻结状态。请不要认为已自动归还;客户端没有自动重试。',
126
- STREAM_ALREADY_CONSUMED: '该幂等键已成功完成过同一请求,本次未重复扣费。',
127
- IDEMPOTENCY_CONFLICT: '该幂等键已用于另一个不同的请求,本次未重复扣费。',
128
- IDEMPOTENCY_RESULT_EXPIRED: '该幂等键上一次保存的结果已经不能再回放。上一次请求已经完成,本次未重复扣费;请核对余额与流水。',
129
- REQUEST_IN_PROGRESS: '该幂等键上一次的请求仍在运行。本次未重复扣费;请等待它完成后核对余额与流水。',
130
- INPUT_INVALID: '请求参数无效。',
131
- SUBMISSION_RETRY_MISMATCH: '该幂等键对应一条待确认的提交,但模型或输入与这次不同。没有提交任何任务;请用原来的参数重新运行。',
132
- SUBMISSION_CREDENTIALS_MISMATCH: '该幂等键对应的待确认提交是用另一组凭据或另一个网关发出的。没有提交任何任务;请用创建它的档案和网关重试。',
133
- SUBMISSION_JOURNAL_UNWRITABLE: '无法写入待确认提交记录。',
134
- SUBMISSION_RECORD_NOT_FOUND: '这个账号和网关下没有以该幂等键记录的待确认提交。',
135
- SUBMISSION_PENDING_IDENTICAL: '已有一次相同的提交以另一个幂等键记录且结果未确认,因此没有用这个键发送任何内容。请先处理那次待确认提交,或等它在记录后 23 小时过期。',
136
- QUOTE_NOT_AVAILABLE: '报价已不可用:已过期、从未签发,或与这次输入不匹配。',
137
- QUOTE_ALREADY_CONSUMED: '该报价已被另一次提交使用。',
138
- SUBMISSION_RECORD_EXPIRED: '该幂等键对应的待确认提交已超过 23 小时。没有重发:网关可能已不再保留它的幂等记录,重放的结果无法安全解读。请先核对任务列表与余额,再决定是否重新提交。',
139
- SUBMISSION_JOURNAL_INVALID: '无法读取待确认提交记录。',
140
- IDEMPOTENCY_KEY_REQUIRED: '必须提供幂等键。',
141
- IDEMPOTENCY_KEY_INVALID: '幂等键不能包含换行符。',
142
- IDEMPOTENCY_KEY_TOO_LONG: '幂等键不得超过 128 字节。',
143
- SDK_STREAM_NOT_IMPLEMENTED: '当前客户端不支持流式对话。请省略 stream 参数或将其设为 false。',
144
- LANG_INVALID: '语言只能是 zh 或 en。',
145
- BALANCE_RESPONSE_INVALID: '余额响应字段不符合约定。',
146
- CATALOG_RESPONSE_INVALID: '模型目录响应不符合约定。',
147
- CHAT_BILLING_INVALID: '本次对话返回的结算信息无法使用。',
148
- CHAT_EMPTY_RESPONSE: '模型没有返回可显示的回复。',
149
- JOB_ID_MISMATCH: '响应中的任务 ID 与请求的任务不一致。',
150
- JOB_RESPONSE_INVALID: '任务响应不符合约定。',
151
- MODEL_INPUT_SCHEMA_INVALID: '该模型没有可用的输入结构。',
152
- MODEL_PARAM_INVALID: '模型参数的值无效。',
153
- MODEL_PARAM_ALIAS_CONFLICT: '同一模型参数使用了两个不同字段名。请只保留一个字段名;没有发送请求。',
154
- SUBMISSION_INPUT_POLICY_CHANGED: '默认值补全改变了待确认提交的输入。请恢复记录的原请求体,不要创建新任务。',
155
- MODEL_PARAM_UNKNOWN: '该模型未声明这个参数。',
156
- MODEL_PARAM_SYNTAX_INVALID: 'key=value 参数语法无效。',
157
- QUOTE_EXPIRED: '该报价已过期。请重新报价。',
158
- QUOTE_RESPONSE_INVALID: '报价响应不符合约定。',
159
- QUOTE_CREDIT_LIMIT: '本次报价超过你设置的花费上限。',
160
- REQUEST_TIMEOUT: '网关在客户端超时前没有响应。',
161
- RESPONSE_JSON_INVALID: '网关响应不是有效的 JSON。',
162
- RESPONSE_TOO_LARGE: '网关响应超过客户端大小限制。',
163
- TIMEOUT_INVALID: '超时时间必须是正整数。',
164
- VIDEO_DURATION_INVALID: '该视频时长对这个模型无效。',
165
- WAIT_OPTIONS_INVALID: '等待间隔或超时时间无效。',
166
- WAIT_TIMEOUT: '等待任务超时。任务可能仍在运行。',
167
- UPSTREAM_UNAVAILABLE: '上游服务暂时不可用。',
168
- UPSTREAM_TIMEOUT: '上游服务超时。',
169
- UPSTREAM_INVALID_RESPONSE: '上游服务返回了网关无法使用的响应。',
170
- STREAM_FAILED: '对话流在完成前失败了。',
171
- STREAM_ROUTE_NOT_AVAILABLE: '这个模型当前没有可用线路。',
172
- STREAM_RUNTIME_UNAVAILABLE: '对话运行时暂时不可用。',
173
- MODEL_NOT_AVAILABLE: '该模型当前不可用。',
174
- CONTENT_REJECTED: '内容未通过审核。',
175
- IDEMPOTENCY_INPUT_INVALID: '幂等键只能是不含空格的可见 ASCII 字符。',
176
- CONFIG_INVALID: '本地配置文件不可用。',
177
- CONFIG_UNREADABLE: '本地配置文件无法读取或写入。',
178
- CONFIG_UNSAFE_PATH: '配置路径不能使用符号链接或硬链接。',
179
- CONFIG_NOT_A_FILE: '配置路径存在,但不是普通文件。',
180
- CHAT_REQUEST_INVALID: '对话请求无效。',
181
- CANCELLED: '请求已取消。',
182
- ACCOUNT_NOT_ELIGIBLE: '此账号不能使用所请求的模型。',
183
- CHAT_UPSTREAM_ERROR: '模型返回了内部错误,而不是回答。',
184
- UNAUTHORIZED: '密钥未被接受。请检查 LUMENKEY_API_KEY,或运行 lumenkey login。',
185
- FORBIDDEN: '这把密钥不能执行该操作。',
186
- INSUFFICIENT_CREDITS: '算力点不足,无法完成本次请求。',
187
- NOT_FOUND: '网关没有这个资源。',
188
- RATE_LIMITED: '请求过于频繁,请稍后再试。',
189
- GATEWAY_REDIRECTED: '网关返回了重定向,客户端没有跟随。',
190
- JOB_CANCELLED: '任务已取消。',
191
- JOB_EXPIRED: '任务在完成前已过期。',
192
- JOB_OUTPUT_MISSING: '任务已结束,但没有结果地址。',
193
- JOB_TERMINAL_FAILURE: '任务已结束,没有可用结果。',
194
- DOWNLOAD_URL_INVALID: '结果地址无法下载。',
195
- DOWNLOAD_TOO_LARGE: '结果超过 128 MiB,没有保存。',
196
- DOWNLOAD_EXISTS: '目标文件已存在,没有覆盖。',
197
- DOWNLOAD_IS_DIRECTORY: '目标是目录;请选择包含文件名的路径。',
198
- DOWNLOAD_NETWORK_ERROR: '无法通过网络下载结果。',
199
- DOWNLOAD_TIMEOUT: '下载结果超时。',
200
- DOWNLOAD_INTERRUPTED: '已停止本机下载,生成结果仍然可用。',
201
- DOWNLOAD_WRITE_ERROR: '无法把结果保存到该位置。',
202
- DOWNLOAD_HTTP_ERROR: '结果服务器拒绝了下载请求。',
203
- DOWNLOAD_PATH_NOT_ABSOLUTE: '下载路径必须是绝对路径。',
204
- WAIT_INTERRUPTED: '已停止等待,没有取消任务。',
205
- REQUEST_ABORTED: '已停止本机请求。',
206
- PROFILE_INVALID: '档案文件格式无效,没有覆盖原文件。',
207
- PROFILE_UNREADABLE: '档案文件无法读取或安全写入。',
208
- PROFILE_MISSING: '所选档案不存在。',
209
- PROFILE_EXISTS: '该档案已存在。',
210
- INPUT_CLOSED: '回答前标准输入已关闭。',
211
- CANCEL_CONFIRMATION_REQUIRED: '未加 --yes,拒绝取消。',
212
- CREDIT_LIMIT_INVALID: '花费上限必须是非负安全整数。',
213
- CLI_COMMAND_FAILED: '命令未能完成。',
113
+ DOWNLOAD_ROOT_INVALID: () => zhText('downloadRootInvalid'),
114
+ DOWNLOAD_OUTSIDE_ROOT: () => zhText('downloadOutsideRoot'),
115
+ DOWNLOAD_UNSAFE_PATH: () => zhText('downloadUnsafePath'),
116
+ API_KEY_NOT_CONFIGURED: () => zhText("m213"),
117
+ INVALID_API_KEY: () => zhText("m212"),
118
+ MODEL_REQUIRED: () => zhText("m211"),
119
+ MODEL_NOT_IN_CATALOG: () => zhText("m210"),
120
+ BASE_URL_INVALID: () => zhText("m209"),
121
+ TRANSPORT_FAILED: () => zhText("m208"),
122
+ PROXY_URL_INVALID: () => zhText("m207"),
123
+ PROXY_PROTOCOL_UNSUPPORTED: () => zhText("m206"),
124
+ CLI_ARGUMENTS_INVALID: () => zhText("m205"),
125
+ HTTP_ERROR: () => zhText("m204"),
126
+ API_REQUEST_FAILED: () => zhText("m204"),
127
+ JOB_ID_INVALID: () => zhText("m203"),
128
+ JOB_NOT_FOUND: () => zhText("m202"),
129
+ CHAT_CREDIT_LIMIT_INVALID: () => zhText("m201"),
130
+ CHAT_CREDIT_LIMIT_EXCEEDED: () => zhText("m200"),
131
+ STREAM_SETTLEMENT_PENDING: () => zhText("m199"),
132
+ STREAM_INPUT_SETTLEMENT_PENDING: () => zhText("m198"),
133
+ STREAM_ALREADY_CONSUMED: () => zhText("m197"),
134
+ IDEMPOTENCY_CONFLICT: () => zhText("m196"),
135
+ IDEMPOTENCY_RESULT_EXPIRED: () => zhText("m195"),
136
+ REQUEST_IN_PROGRESS: () => zhText("m194"),
137
+ INPUT_INVALID: () => zhText("m193"),
138
+ SUBMISSION_RETRY_MISMATCH: () => zhText("m192"),
139
+ SUBMISSION_CREDENTIALS_MISMATCH: () => zhText("m191"),
140
+ SUBMISSION_JOURNAL_UNWRITABLE: () => zhText("m190"),
141
+ SUBMISSION_RECORD_NOT_FOUND: () => zhText("m22"),
142
+ SUBMISSION_PENDING_IDENTICAL: () => zhText("m189"),
143
+ QUOTE_NOT_AVAILABLE: () => zhText("m188"),
144
+ QUOTE_ALREADY_CONSUMED: () => zhText("m187"),
145
+ SUBMISSION_RECORD_EXPIRED: () => zhText("m186"),
146
+ SUBMISSION_JOURNAL_INVALID: () => zhText("m185"),
147
+ IDEMPOTENCY_KEY_REQUIRED: () => zhText("m99"),
148
+ IDEMPOTENCY_KEY_INVALID: () => zhText("m97"),
149
+ IDEMPOTENCY_KEY_TOO_LONG: () => zhText("m98"),
150
+ SDK_STREAM_NOT_IMPLEMENTED: () => zhText("m184"),
151
+ LANG_INVALID: () => zhText("m183"),
152
+ BALANCE_RESPONSE_INVALID: () => zhText("m182"),
153
+ CATALOG_RESPONSE_INVALID: () => zhText("m181"),
154
+ CHAT_BILLING_INVALID: () => zhText("m180"),
155
+ CHAT_EMPTY_RESPONSE: () => zhText("m179"),
156
+ JOB_ID_MISMATCH: () => zhText("m178"),
157
+ JOB_RESPONSE_INVALID: () => zhText("m177"),
158
+ MODEL_INPUT_SCHEMA_INVALID: () => zhText("m176"),
159
+ MODEL_PARAM_INVALID: () => zhText("m175"),
160
+ MODEL_PARAM_ALIAS_CONFLICT: () => zhText("m174"),
161
+ SUBMISSION_INPUT_POLICY_CHANGED: () => zhText("m173"),
162
+ MODEL_PARAM_UNKNOWN: () => zhText("m172"),
163
+ MODEL_PARAM_SYNTAX_INVALID: () => zhText("m171"),
164
+ QUOTE_EXPIRED: () => zhText("m170"),
165
+ QUOTE_RESPONSE_INVALID: () => zhText("m169"),
166
+ QUOTE_CREDIT_LIMIT: () => zhText("m168"),
167
+ REQUEST_TIMEOUT: () => zhText("m167"),
168
+ RESPONSE_JSON_INVALID: () => zhText("m166"),
169
+ RESPONSE_TOO_LARGE: () => zhText("m165"),
170
+ TIMEOUT_INVALID: () => zhText("m164"),
171
+ VIDEO_DURATION_INVALID: () => zhText("m163"),
172
+ WAIT_OPTIONS_INVALID: () => zhText("m162"),
173
+ WAIT_TIMEOUT: () => zhText("m161"),
174
+ UPSTREAM_UNAVAILABLE: () => zhText("m160"),
175
+ UPSTREAM_TIMEOUT: () => zhText("m159"),
176
+ UPSTREAM_INVALID_RESPONSE: () => zhText("m158"),
177
+ STREAM_FAILED: () => zhText("m157"),
178
+ STREAM_ROUTE_NOT_AVAILABLE: () => zhText("m156"),
179
+ STREAM_RUNTIME_UNAVAILABLE: () => zhText("m155"),
180
+ MODEL_NOT_AVAILABLE: () => zhText("m154"),
181
+ CONTENT_REJECTED: () => zhText("m153"),
182
+ IDEMPOTENCY_INPUT_INVALID: () => zhText("m152"),
183
+ CONFIG_INVALID: () => zhText("m151"),
184
+ CONFIG_UNREADABLE: () => zhText("m150"),
185
+ CONFIG_UNSAFE_PATH: () => zhText("m149"),
186
+ CONFIG_NOT_A_FILE: () => zhText("m148"),
187
+ CHAT_REQUEST_INVALID: () => zhText("m147"),
188
+ CANCELLED: () => zhText("m146"),
189
+ ACCOUNT_NOT_ELIGIBLE: () => zhText("m145"),
190
+ CHAT_UPSTREAM_ERROR: () => zhText("m144"),
191
+ UNAUTHORIZED: () => zhText("m143"),
192
+ FORBIDDEN: () => zhText("m142"),
193
+ INSUFFICIENT_CREDITS: () => zhText("m141"),
194
+ NOT_FOUND: () => zhText("m140"),
195
+ RATE_LIMITED: () => zhText("m139"),
196
+ GATEWAY_REDIRECTED: () => zhText("m138"),
197
+ JOB_CANCELLED: () => zhText("m137"),
198
+ JOB_EXPIRED: () => zhText("m136"),
199
+ JOB_OUTPUT_MISSING: () => zhText("m135"),
200
+ JOB_TERMINAL_FAILURE: () => zhText("m134"),
201
+ DOWNLOAD_URL_INVALID: () => zhText("m133"),
202
+ DOWNLOAD_TOO_LARGE: () => zhText("m132"),
203
+ DOWNLOAD_EXISTS: () => zhText("m131"),
204
+ DOWNLOAD_IS_DIRECTORY: () => zhText("m130"),
205
+ DOWNLOAD_NETWORK_ERROR: () => zhText("m129"),
206
+ DOWNLOAD_TIMEOUT: () => zhText("m128"),
207
+ DOWNLOAD_INTERRUPTED: () => zhText("m127"),
208
+ DOWNLOAD_WRITE_ERROR: () => zhText("m126"),
209
+ DOWNLOAD_HTTP_ERROR: () => zhText("m125"),
210
+ DOWNLOAD_PATH_NOT_ABSOLUTE: () => zhText("m124"),
211
+ WAIT_INTERRUPTED: () => zhText("m123"),
212
+ REQUEST_ABORTED: () => zhText("m122"),
213
+ PROFILE_INVALID: () => zhText("m121"),
214
+ PROFILE_UNREADABLE: () => zhText("m120"),
215
+ PROFILE_MISSING: () => zhText("m119"),
216
+ PROFILE_EXISTS: () => zhText("m118"),
217
+ INPUT_CLOSED: () => zhText("m117"),
218
+ CANCEL_CONFIRMATION_REQUIRED: () => zhText("m116"),
219
+ CREDIT_LIMIT_INVALID: () => zhText("m115"),
220
+ CLI_COMMAND_FAILED: () => zhText("m114"),
214
221
  };
215
222
  export function langFlag(argv = process.argv) {
216
223
  const args = argv.slice(2);
@@ -229,7 +236,7 @@ function primarySubtag(raw) {
229
236
  return (raw.trim().replace(/-/g, '_').split(/[._@]/)[0] ?? '').toLowerCase();
230
237
  }
231
238
  function localeLang(env) {
232
- return primarySubtag(env.LC_ALL || env.LANG || '') === 'en' ? 'en' : 'zh';
239
+ return primarySubtag(env.LC_ALL || env.LC_MESSAGES || env.LANG || '') === 'en' ? 'en' : 'zh';
233
240
  }
234
241
  /** --lang zh|en, then LUMENKEY_LANG, then LC_ALL / LANG, then zh. Invalid values are ignored here. */
235
242
  export function resolveLang(argv = process.argv, env = process.env) {
@@ -247,22 +254,21 @@ export function hasErrorSentence(code) {
247
254
  return Object.prototype.hasOwnProperty.call(EN, code);
248
255
  }
249
256
  export function errorSentence(code, lang = resolveLang()) {
250
- const table = lang === 'zh' ? ZH : EN;
251
- return table[code] ?? code;
257
+ return lang === 'zh' ? ZH[code]?.() ?? code : EN[code] ?? code;
252
258
  }
253
259
  export function transportFailureDetail(baseUrl, lang = resolveLang()) {
254
260
  return lang === 'zh'
255
- ? `无法连接网关 ${baseUrl},请检查网络与网关地址。`
261
+ ? (zhText("m112") + (baseUrl) + zhText("m113"))
256
262
  : `Network connection to ${baseUrl} failed. Check the gateway address and connection.`;
257
263
  }
258
264
  export function proxyTransportDetail(baseUrl, lang = resolveLang()) {
259
265
  return lang === 'zh'
260
- ? `通过 HTTP(S)_PROXY 或 ALL_PROXY 连接网关 ${baseUrl} 失败。请检查代理,不要把代理地址当成网关故障。`
266
+ ? (zhText("m110") + (baseUrl) + zhText("m111"))
261
267
  : `The request to ${baseUrl} failed through HTTP(S)_PROXY or ALL_PROXY. Check that proxy; this is not a gateway-address failure.`;
262
268
  }
263
269
  export function chatFailureSettlementAdvice(lang = resolveLang()) {
264
270
  return lang === 'zh'
265
- ? '未收到结算信息,本次结算状态未知;算力点可能仍处于冻结状态。请核对余额与流水后再重试。'
271
+ ? zhText("m109")
266
272
  : 'Settlement was not reported and is unknown; credits may still be reserved. Check your balance and ledger before retrying.';
267
273
  }
268
274
  const INVISIBLE_PROMPT = /[\p{Cf}\p{Mn}\p{Me}\p{Zs}\p{Zl}\p{Zp}\u115F\u1160\u2800\u3164\uFFA0]/gu;
@@ -273,23 +279,23 @@ export function isBlankPrompt(value) {
273
279
  export function textCatalogMissDetail(model, lang = resolveLang()) {
274
280
  const shown = JSON.stringify(model);
275
281
  return lang === 'zh'
276
- ? `模型 ${shown} 不在你的可用文本目录中。请使用准确的模型 ID。`
282
+ ? (zhText("m107") + (shown) + zhText("m108"))
277
283
  : `Model ${shown} is not in your available text catalog. Use an exact model ID.`;
278
284
  }
279
285
  export function missingModelSentence(name, lang = resolveLang()) {
280
286
  return lang === 'zh'
281
- ? `没有名为 ${name} 的模型。请运行:lumenkey model search ${name}`
287
+ ? (zhText("m105") + (name) + zhText("m106") + (name) + "")
282
288
  : `No exact model named ${name}. Run: lumenkey model search ${name}`;
283
289
  }
284
290
  export function billingSentence(status, credits, lang = resolveLang()) {
285
291
  if (lang === 'zh') {
286
292
  if (status === 'PENDING')
287
- return '结算尚未确认,算力点可能仍处于冻结状态。';
293
+ return zhText("m104");
288
294
  if (status === 'RELEASED')
289
- return '已扣 0 算力点,预授权已释放。';
295
+ return zhText("m103");
290
296
  if (status === 'SETTLED')
291
- return `已扣 ${credits} 算力点。`;
292
- return '未收到结算信息,本次扣费金额未知。';
297
+ return (zhText("m101") + (credits) + zhText("m102"));
298
+ return zhText("m100");
293
299
  }
294
300
  if (status === 'PENDING')
295
301
  return 'Settlement pending; credits may still be reserved.';
@@ -301,7 +307,7 @@ export function billingSentence(status, credits, lang = resolveLang()) {
301
307
  }
302
308
  export function idempotencyKeyMessages(lang = resolveLang()) {
303
309
  return lang === 'zh'
304
- ? { required: '必须提供幂等键。', max: '幂等键不得超过 128 字节。', breaks: '幂等键不能包含换行符。' }
310
+ ? { required: zhText("m99"), max: zhText("m98"), breaks: zhText("m97") }
305
311
  : {
306
312
  required: 'An idempotency key is required.',
307
313
  max: 'The idempotency key must be at most 128 bytes.',
@@ -313,56 +319,80 @@ export function ui(en, zh, lang = resolveLang()) {
313
319
  }
314
320
  export function waitTimeoutDetail(jobId, timeoutMs, lang = resolveLang()) {
315
321
  const seconds = timeoutMs / 1000;
316
- return ui(`Timed out waiting after ${seconds} seconds. The task may still be running; it was not cancelled. Resume with: lumenkey status ${jobId}`, `等待任务 ${seconds} 秒后超时。任务可能仍在运行,未执行取消。可继续查询:lumenkey status ${jobId}`, lang);
322
+ return ui(`Timed out waiting after ${seconds} seconds. The task may still be running; it was not cancelled. Resume with: lumenkey status ${jobId}`, (zhText("m95") + (seconds) + zhText("m96") + (jobId) + ""), lang);
317
323
  }
318
324
  export function waitInterruptedDetail(jobId, lang = resolveLang()) {
319
- return ui(`${errorSentence('WAIT_INTERRUPTED', 'en')} Resume with: lumenkey status ${jobId}`, `${errorSentence('WAIT_INTERRUPTED', 'zh')} 可继续查询:lumenkey status ${jobId}`, lang);
325
+ return ui(`${errorSentence('WAIT_INTERRUPTED', 'en')} Resume with: lumenkey status ${jobId}`, ("" + (errorSentence('WAIT_INTERRUPTED', 'zh')) + zhText("m94") + (jobId) + ""), lang);
326
+ }
327
+ /** Local display only; recovery's machine deadline stays ISO UTC in the outcome. */
328
+ export function submissionUnknownMessage(recovery, deadline, lang = resolveLang()) {
329
+ const head = ui('Submission result unknown; do not retry with different key or input.', zhText("m93"), lang);
330
+ const pad = (value) => String(value).padStart(2, '0');
331
+ let local;
332
+ if (deadline && Number.isFinite(deadline.getTime())) {
333
+ const offset = -deadline.getTimezoneOffset();
334
+ local = `${deadline.getFullYear()}-${pad(deadline.getMonth() + 1)}-${pad(deadline.getDate())} ${pad(deadline.getHours())}:${pad(deadline.getMinutes())} (${offset >= 0 ? '+' : '-'}${pad(Math.floor(Math.abs(offset) / 60))}:${pad(Math.abs(offset) % 60)})`;
335
+ }
336
+ return `${head} ${RECOVERY_COPY[recovery].message(local, lang)}`;
320
337
  }
321
338
  /** The recovery verb on both surfaces, named wherever a keyless rerun will not do. */
322
339
  export function resumeCommandText(idempotencyKey, lang = resolveLang()) {
323
- return ui(`lumenkey tasks resume ${idempotencyKey} (MCP: lumenkey_resume_submission with idempotencyKey "${idempotencyKey}")`, `lumenkey tasks resume ${idempotencyKey}(MCP:lumenkey_resume_submission 并带 idempotencyKey "${idempotencyKey}")`, lang);
340
+ return ui(`lumenkey tasks resume ${idempotencyKey} (MCP: lumenkey_resume_submission with idempotencyKey "${idempotencyKey}")`, ("lumenkey tasks resume " + (idempotencyKey) + zhText("m92") + (idempotencyKey) + "\")"), lang);
324
341
  }
325
342
  export function submissionUnknownAdvice(idempotencyKey, recovery = 'ledger', recordedAt, lang = resolveLang()) {
326
- const head = ui('The gateway may already have accepted this job and reserved credits. Do not submit again with a different key.', '网关可能已经受理了这次任务并冻结了算力点。不要换用别的幂等键重新提交。', lang);
343
+ const head = ui('The gateway may already have accepted this job and reserved credits. Do not submit again with a different key.', zhText("m91"), lang);
327
344
  const deadline = recordedAt && Number.isFinite(Date.parse(recordedAt)) ? new Date(Date.parse(recordedAt) + 23 * 60 * 60 * 1000).toISOString() : undefined;
328
- if (recovery === 'rerun')
329
- return `${head} ${ui(`To recover, run the same command again with the same arguments${deadline ? ` before ${deadline} (23 hours after the submission was recorded)` : ''}; the client re-sends this recorded submission and the gateway replays the earlier job or creates it once. You can also run ${resumeCommandText(idempotencyKey, 'en')}, or check your balance and ledger first.`, `恢复方法:${deadline ? `在 ${deadline}(记录后 23 小时)之前` : ''}用相同参数再次运行同一命令,客户端会原样重发这次记录的提交,网关会回放之前的任务或只创建一次。也可以运行 ${resumeCommandText(idempotencyKey, 'zh')},或先核对余额与流水。`, lang)}`;
330
- if (recovery === 'resume-command')
331
- return `${head} ${ui(`The recorded submission is still marked in flight, so a plain rerun will not resume it. To recover, run ${resumeCommandText(idempotencyKey, 'en')}; it re-sends the recorded body and the gateway replays the earlier job or creates it once. Or check your balance and ledger first.`, `记录的提交仍标记为进行中,直接重跑不会接续它。恢复方法:运行 ${resumeCommandText(idempotencyKey, 'zh')},它会原样重发记录的请求体,网关会回放之前的任务或只创建一次;也可以先核对余额与流水。`, lang)}`;
332
- if (recovery === 'expired')
333
- return `${head} ${ui(`The recorded submission is past the 23-hour window in which it can be re-sent safely, so it will not be resumed. Check your job list and balance before submitting again.`, `记录的提交已超过可安全重发的 23 小时窗口,不会再接续。请先核对任务列表与余额,再决定是否重新提交。`, lang)}`;
334
- return `${head} ${ui(`No local record of this submission remains (idempotency key ${idempotencyKey}), so it cannot be re-sent from here. Check your job list and balance before submitting again.`, `本机已没有这次提交的记录(幂等键 ${idempotencyKey}),无法从这里重发。请先核对任务列表与余额,再决定是否重新提交。`, lang)}`;
345
+ return `${head} ${RECOVERY_COPY[recovery].advice(idempotencyKey, deadline, lang)}`;
335
346
  }
347
+ /** Each recovery state owns both short and compatibility copy: no independent fallthroughs. */
348
+ const RECOVERY_COPY = {
349
+ rerun: {
350
+ message: (local, lang) => ui(`Run the same command again with the same arguments${local ? ` before ${local}` : ''}; or use lumenkey tasks resume <idempotencyKey> (MCP: lumenkey_resume_submission).`, (zhText("m89") + (local ? (zhText("m83") + (local) + "") : '') + zhText("m90")), lang),
351
+ advice: (idempotencyKey, deadline, lang) => ui(`To recover, run the same command again with the same arguments${deadline ? ` before ${deadline} (23 hours after the submission was recorded)` : ''}; the client re-sends this recorded submission and the gateway replays the earlier job or creates it once. You can also run ${resumeCommandText(idempotencyKey, 'en')}, or check your balance and ledger first.`, (zhText("m84") + (deadline ? (zhText("m85") + (deadline) + zhText("m86")) : '') + zhText("m87") + (resumeCommandText(idempotencyKey, 'zh')) + zhText("m88")), lang),
352
+ },
353
+ 'resume-command': {
354
+ message: (local, lang) => ui(`Record still in flight; a plain rerun will not resume it. Use lumenkey tasks resume <idempotencyKey> (MCP: lumenkey_resume_submission)${local ? ` before ${local}` : ''}.`, (zhText("m82") + (local ? (zhText("m83") + (local) + "") : '') + "。"), lang),
355
+ advice: (idempotencyKey, _deadline, lang) => ui(`The recorded submission is still marked in flight, so a plain rerun will not resume it. To recover, run ${resumeCommandText(idempotencyKey, 'en')}; it re-sends the recorded body and the gateway replays the earlier job or creates it once. Or check your balance and ledger first.`, (zhText("m80") + (resumeCommandText(idempotencyKey, 'zh')) + zhText("m81")), lang),
356
+ },
357
+ expired: {
358
+ message: (local, lang) => ui(`Past the 23-hour window${local ? ` (${local})` : ''}; check your job list and balance.`, (zhText("m78") + (local ? `(${local})` : '') + zhText("m79")), lang),
359
+ advice: (_idempotencyKey, _deadline, lang) => ui(`The recorded submission is past the 23-hour window in which it can be re-sent safely, so it will not be resumed. Check your job list and balance before submitting again.`, zhText("m77"), lang),
360
+ },
361
+ ledger: {
362
+ message: (_local, lang) => ui('Check your job list and balance; no safe replay is available here.', zhText("m76"), lang),
363
+ advice: (idempotencyKey, _deadline, lang) => ui(`No local record of this submission remains (idempotency key ${idempotencyKey}), so it cannot be re-sent from here. Check your job list and balance before submitting again.`, (zhText("m74") + (idempotencyKey) + zhText("m75")), lang),
364
+ },
365
+ };
336
366
  /** A replay was refused inside the gateway's create step, which is only reached when no job exists for the key. */
337
367
  export function replaySettledAdvice(lang = resolveLang()) {
338
- return ui('The earlier submission with this idempotency key was never created, so nothing was charged for it. It is safe to run the command again to get a new quote.', '该幂等键之前的提交从未被创建,没有产生扣费。可以安全地重新运行命令获取新的报价。', lang);
368
+ return ui('The earlier submission with this idempotency key was never created, so nothing was charged for it. It is safe to run the command again to get a new quote.', zhText("m73"), lang);
339
369
  }
340
370
  /** A replay met a record for the same key with a different body: the earlier job exists, but this client cannot see it. */
341
371
  export function replayConflictAdvice(lang = resolveLang()) {
342
- return ui('A job for this idempotency key already exists with a different request body. Check your job list and balance before submitting again; do not retry with a new key.', '该幂等键已有一个请求体不同的任务。请先核对任务列表与余额,再决定是否重新提交;不要换新键重试。', lang);
372
+ return ui('A job for this idempotency key already exists with a different request body. Check your job list and balance before submitting again; do not retry with a new key.', zhText("m72"), lang);
343
373
  }
344
374
  /** Chat requests are replayed by key, so the safe retry is the identical request with the same key. */
345
375
  export function chatRetryAdvice(idempotencyKey, lang = resolveLang()) {
346
- return ui(`To retry safely, send the identical request again with the same idempotency key ${idempotencyKey}; the gateway replays the earlier result instead of charging twice.`, `安全重试方法:用同一个幂等键 ${idempotencyKey} 再次发送完全相同的请求;网关会回放上一次结果,不会重复扣费。`, lang);
376
+ return ui(`To retry safely, send the identical request again with the same idempotency key ${idempotencyKey}; the gateway replays the earlier result instead of charging twice.`, (zhText("m70") + (idempotencyKey) + zhText("m71")), lang);
347
377
  }
348
378
  export function downloadPathNotAbsoluteDetail(lang = resolveLang()) {
349
- return ui('The download path must be absolute, or start with ~/ for the home directory. This server cannot resolve a relative path on behalf of the caller.', '下载路径必须是绝对路径,或以 ~/ 开头表示主目录。服务无法代替调用方解析相对路径。', lang);
379
+ return ui('The download path must be absolute, or start with ~/ for the home directory. This server cannot resolve a relative path on behalf of the caller.', zhText("m69"), lang);
350
380
  }
351
381
  export function jobStatusText(status, lang = resolveLang()) {
352
382
  const names = {
353
- accepted: '已受理', policy_checking: '策略检查中', queued: '排队中', submitting: '提交中',
354
- provider_running: '生成中', output_checking: '结果检查中', succeeded: '已完成',
355
- rejected: '已拒绝', failed: '失败', cancelled: '已取消', expired: '已过期',
383
+ accepted: zhText("m68"), policy_checking: zhText("m67"), queued: zhText("m66"), submitting: zhText("m65"),
384
+ provider_running: zhText("m64"), output_checking: zhText("m63"), succeeded: zhText("m62"),
385
+ rejected: zhText("m61"), failed: zhText("m60"), cancelled: zhText("m59"), expired: zhText("m58"),
356
386
  };
357
387
  return lang === 'zh' && names[status] ? `${names[status]} (${status})` : status;
358
388
  }
359
389
  export function blankPromptSentence(kind, lang = resolveLang()) {
360
390
  if (lang === 'zh') {
361
391
  if (kind === 'flag')
362
- return '必须提供 --prompt,且不能为空白。';
392
+ return zhText("m57");
363
393
  if (kind === 'field')
364
- return '必须提供 prompt,且不能为空白。';
365
- return '用户提示不能为空白。';
394
+ return zhText("m56");
395
+ return zhText("m55");
366
396
  }
367
397
  if (kind === 'flag')
368
398
  return '--prompt is required and must not be blank.';
@@ -372,16 +402,16 @@ export function blankPromptSentence(kind, lang = resolveLang()) {
372
402
  }
373
403
  /** Surface-neutral: neither a CLI flag nor an MCP field is named. */
374
404
  export function promptRequiredSentence(lang = resolveLang()) {
375
- return ui('A prompt is required and must not be blank.', '必须提供提示词,且不能为空白。', lang);
405
+ return ui('A prompt is required and must not be blank.', zhText("m54"), lang);
376
406
  }
377
407
  export function spendingCapSentence(label, lang = resolveLang()) {
378
408
  return lang === 'zh'
379
- ? `${label} 必须是非负整数。`
409
+ ? ("" + (label) + zhText("m53"))
380
410
  : `${label} must be a non-negative integer.`;
381
411
  }
382
412
  export function quoteCapSentence(label, lang = resolveLang()) {
383
413
  return lang === 'zh'
384
- ? `本次报价超过你设置的 ${label} 上限。`
414
+ ? (zhText("m51") + (label) + zhText("m52"))
385
415
  : `This quote is above the ${label} limit you set.`;
386
416
  }
387
417
  const INTERNAL_ERROR_TEXT = /^\s*\[error:\s*[a-z0-9_]+\]\s*$/i;
@@ -60,6 +60,12 @@ export interface Failure extends LegacyJobMirror {
60
60
  quoteId?: string;
61
61
  submission?: Submission;
62
62
  resumed?: boolean;
63
+ /** Recovery state of an unknown submission; never an instruction to create a new job. */
64
+ recovery?: UnknownRecovery;
65
+ /** CLI argv, not a shell command string. Absent when replay is unsafe or no record remains. */
66
+ recoveryCommand?: string[];
67
+ /** The recorded 23-hour replay deadline, ISO UTC. */
68
+ resumeDeadline?: string;
63
69
  billing?: ChatBilling;
64
70
  /** The gateway task record, including its raw `error` object. */
65
71
  job?: GenerationJobDetail;
@@ -1,6 +1,7 @@
1
1
  import { LumenKeyError, parseChatBilling } from './client.js';
2
- import { errorSentence, submissionUnknownAdvice } from './locale.js';
2
+ import { errorSentence, submissionUnknownAdvice, submissionUnknownMessage } from './locale.js';
3
3
  import { assertJobResult } from './poller.js';
4
+ import { RESUMABLE_WINDOW_MS } from './submission-journal.js';
4
5
  export function chatText(response) {
5
6
  const message = response.choices?.[0]?.message;
6
7
  if (typeof message?.content === 'string' && message.content.trim())
@@ -24,7 +25,8 @@ function submissionOf(value) {
24
25
  */
25
26
  export function failure(error, ctx = {}) {
26
27
  const e = (error && typeof error === 'object' ? error : {});
27
- const code = typeof e.code === 'string' && CODE.test(e.code) ? e.code : 'CLI_COMMAND_FAILED';
28
+ const expired = e.code === 'SUBMISSION_RECORD_EXPIRED' || ctx.recovery === 'expired';
29
+ const code = expired ? 'SUBMISSION_RECORD_EXPIRED' : typeof e.code === 'string' && CODE.test(e.code) ? e.code : 'CLI_COMMAND_FAILED';
28
30
  const job = ctx.job ?? (e.job && typeof e.job === 'object' ? e.job : undefined);
29
31
  const jobId = ctx.jobId ?? text(e.jobId) ?? job?.jobId;
30
32
  const jobStatus = ctx.jobStatus ?? text(e.jobStatus) ?? job?.status;
@@ -42,8 +44,16 @@ export function failure(error, ctx = {}) {
42
44
  const pendingAgeSeconds = ctx.pendingAgeSeconds ?? (typeof e.pendingAgeSeconds === 'number' ? e.pendingAgeSeconds : undefined);
43
45
  const waitStatus = code === 'WAIT_TIMEOUT' ? 'TIMEOUT' : code === 'WAIT_INTERRUPTED' ? 'INTERRUPTED' : undefined;
44
46
  // IDEMPOTENCY_CONFLICT carries its own, more specific recovery text from the use case.
45
- const advice = submission === 'unknown' && idempotencyKey && code !== 'IDEMPOTENCY_CONFLICT' ? ` ${submissionUnknownAdvice(idempotencyKey, ctx.recovery ?? 'ledger', ctx.recordedAt)}` : '';
46
- const message = `${error instanceof LumenKeyError ? error.publicMessage : error instanceof Error ? error.message : errorSentence(code)}${advice}`;
47
+ const recovery = expired ? 'expired' : submission === 'unknown' ? code === 'IDEMPOTENCY_CONFLICT' ? 'ledger' : ctx.recovery ?? 'ledger' : undefined;
48
+ const recordedAt = ctx.recordedAt ?? text(e.recordedAt);
49
+ const advice = (submission === 'unknown' || expired) && idempotencyKey && code !== 'IDEMPOTENCY_CONFLICT' ? ` ${submissionUnknownAdvice(idempotencyKey, recovery ?? 'ledger', recordedAt)}` : '';
50
+ const recorded = recordedAt ? Date.parse(recordedAt) : NaN;
51
+ const deadline = Number.isFinite(recorded) ? new Date(recorded + RESUMABLE_WINDOW_MS) : undefined;
52
+ const resumeDeadline = recovery && recovery !== 'ledger' ? deadline?.toISOString() : undefined;
53
+ const recoveryCommand = recovery === 'rerun' || recovery === 'resume-command'
54
+ ? idempotencyKey ? ['lumenkey', 'tasks', 'resume', `--key=${idempotencyKey}`] : undefined : undefined;
55
+ const message = recovery ? submissionUnknownMessage(recovery, deadline)
56
+ : `${error instanceof LumenKeyError ? error.publicMessage : error instanceof Error ? error.message : errorSentence(code)}${advice}`;
47
57
  const legacy = `${error instanceof Error ? error.message : errorSentence(code)}${advice}`;
48
58
  const { error: _jobError, status: _jobState, jobId: _id, ...mirror } = job ?? {};
49
59
  return {
@@ -57,6 +67,9 @@ export function failure(error, ctx = {}) {
57
67
  ...(quoteId ? { quoteId } : {}),
58
68
  ...(submission ? { submission } : {}),
59
69
  ...(resumed !== undefined ? { resumed } : {}),
70
+ ...(recovery ? { recovery } : {}),
71
+ ...(recoveryCommand ? { recoveryCommand } : {}),
72
+ ...(resumeDeadline ? { resumeDeadline } : {}),
60
73
  ...(billing ? { billing } : {}),
61
74
  ...(job ? { job } : {}),
62
75
  ...(pendingIdempotencyKey ? { pendingIdempotencyKey } : {}),
@@ -6,5 +6,7 @@
6
6
  */
7
7
  export type OutputPathPolicy = 'resolve-cwd' | 'absolute-only';
8
8
  /** Expand a leading `~` / `~/` (never `~user`) to the home directory, then apply the surface policy. */
9
- export declare function resolveOutputPath(raw: string, policy: OutputPathPolicy, home?: string): string;
9
+ export declare function resolveOutputPath(raw: string, policy: OutputPathPolicy, home?: string, downloadPolicy?: {
10
+ configuredRoot: string | undefined;
11
+ }): string;
10
12
  //# sourceMappingURL=output-path.d.ts.map
@@ -2,14 +2,17 @@ import os from 'node:os';
2
2
  import path from 'node:path';
3
3
  import { LumenKeyError } from './client.js';
4
4
  import { downloadPathNotAbsoluteDetail } from './locale.js';
5
+ import { assertDownloadPath } from './download-root.js';
5
6
  /** Expand a leading `~` / `~/` (never `~user`) to the home directory, then apply the surface policy. */
6
- export function resolveOutputPath(raw, policy, home = os.homedir()) {
7
+ export function resolveOutputPath(raw, policy, home = os.homedir(), downloadPolicy) {
8
+ if (raw.split(/[\\/]/).includes('..'))
9
+ throw new LumenKeyError('DOWNLOAD_OUTSIDE_ROOT');
7
10
  let value = raw;
8
11
  if (value === '~' || value.startsWith('~/') || (process.platform === 'win32' && value.startsWith('~\\')))
9
12
  value = path.join(home, value.slice(1));
10
13
  if (policy === 'absolute-only' && !path.isAbsolute(value)) {
11
14
  throw Object.assign(new LumenKeyError('DOWNLOAD_PATH_NOT_ABSOLUTE', undefined, undefined, downloadPathNotAbsoluteDetail()), { path: raw });
12
15
  }
13
- return path.resolve(value);
16
+ return assertDownloadPath(value, downloadPolicy);
14
17
  }
15
18
  //# sourceMappingURL=output-path.js.map