deepccc 0.2.22 → 0.2.23

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.
@@ -41,6 +41,12 @@ export interface DecisionMetrics {
41
41
  contextChars: number;
42
42
  }
43
43
  export type DecisionFailureStatus = "timed_out" | "cancelled" | "invalid_output" | "step_limit" | "tool_error" | "context_limit" | "error";
44
+ export interface DecisionDiagnostic {
45
+ /** Fixed categories only: no provider messages, input values or credentials. */
46
+ code: "provider_error" | "tool_input_invalid" | "unknown_tool" | "schema_validation_failed" | "protocol_error" | "output_truncated" | "incomplete_response" | "deadline_exceeded" | "cancelled" | "budget_exceeded" | "tool_execution_failed" | "internal_error";
47
+ stage: "provider" | "validation" | "protocol" | "runtime" | "tool";
48
+ httpStatus?: number;
49
+ }
44
50
  export type DecisionResult<T> = {
45
51
  requestId: string;
46
52
  stateVersion: string | number;
@@ -51,6 +57,7 @@ export type DecisionResult<T> = {
51
57
  } | {
52
58
  status: DecisionFailureStatus;
53
59
  message: string;
60
+ diagnostic: DecisionDiagnostic;
54
61
  });
55
62
  /** Reusable model client; decisions do not share mutable history or invoke context compaction. */
56
63
  export declare class DecisionSession<T> {
@@ -6,11 +6,30 @@ import { performance } from "node:perf_hooks";
6
6
  import { config, normalizeDeepCccProvider } from "./config.js";
7
7
  class DecisionFailure extends Error {
8
8
  status;
9
- constructor(status, message) {
9
+ diagnostic;
10
+ constructor(status, message, diagnostic) {
10
11
  super(message);
11
12
  this.status = status;
13
+ this.diagnostic = diagnostic;
12
14
  }
13
15
  }
16
+ function failureDiagnostic(status, error) {
17
+ if (status === "timed_out")
18
+ return { code: "deadline_exceeded", stage: "runtime" };
19
+ if (status === "cancelled")
20
+ return { code: "cancelled", stage: "runtime" };
21
+ if (error instanceof DecisionFailure && error.diagnostic)
22
+ return error.diagnostic;
23
+ if (status === "context_limit" || status === "step_limit")
24
+ return { code: "budget_exceeded", stage: "runtime" };
25
+ if (status === "tool_error")
26
+ return { code: "tool_execution_failed", stage: "tool" };
27
+ return { code: status === "invalid_output" ? "protocol_error" : "internal_error", stage: "runtime" };
28
+ }
29
+ function providerDiagnostic(error) {
30
+ const status = error && typeof error === "object" && "statusCode" in error ? error.statusCode : undefined;
31
+ return { code: "provider_error", stage: "provider", ...(typeof status === "number" && Number.isInteger(status) && status >= 100 && status <= 599 ? { httpStatus: status } : {}) };
32
+ }
14
33
  const SUBMIT = "submit_result";
15
34
  const DEADLINE = Symbol("decision deadline");
16
35
  function positive(value, fallback, name) {
@@ -133,7 +152,7 @@ export class DecisionSession {
133
152
  const status = signal.aborted ? signal.reason === DEADLINE ? "timed_out" : "cancelled"
134
153
  : performance.now() >= deadline ? "timed_out" : error instanceof DecisionFailure ? error.status : "error";
135
154
  // Never echo provider errors, credentials, tool payloads or partially generated output.
136
- outcome = { status, message: error instanceof DecisionFailure ? error.message : "Decision did not complete successfully" };
155
+ outcome = { status, message: error instanceof DecisionFailure ? error.message : "Decision did not complete successfully", diagnostic: failureDiagnostic(status, error) };
137
156
  }
138
157
  finally {
139
158
  clearTimeout(timer);
@@ -155,7 +174,7 @@ export class DecisionSession {
155
174
  if (!calls.length || ids.size !== calls.length)
156
175
  throw new DecisionFailure("invalid_output", "Expected unique structured tool calls");
157
176
  if (calls.some(c => c.toolName !== SUBMIT && !Object.hasOwn(this.tools, c.toolName))) {
158
- throw new DecisionFailure("invalid_output", "Tool is not allowed");
177
+ throw new DecisionFailure("invalid_output", "Tool is not allowed", { code: "unknown_tool", stage: "protocol" });
159
178
  }
160
179
  if (calls.some(c => c.toolName === SUBMIT)) {
161
180
  if (calls.length !== 1)
@@ -163,7 +182,7 @@ export class DecisionSession {
163
182
  const validated = await abortable(Promise.resolve().then(() => this.output.validate(calls[0].input)), signal);
164
183
  check();
165
184
  if (!validated.success)
166
- throw new DecisionFailure("invalid_output", "Result validation failed");
185
+ throw new DecisionFailure("invalid_output", "Result validation failed", { code: "schema_validation_failed", stage: "validation" });
167
186
  return validated.value;
168
187
  }
169
188
  if (step + 1 >= this.limits.steps)
@@ -178,7 +197,7 @@ export class DecisionSession {
178
197
  const validated = await abortable(Promise.resolve().then(() => asSchema(definition.inputSchema).validate(call.input)), signal);
179
198
  check();
180
199
  if (!validated.success)
181
- throw new DecisionFailure("invalid_output", "Tool input validation failed");
200
+ throw new DecisionFailure("invalid_output", "Tool input validation failed", { code: "tool_input_invalid", stage: "validation" });
182
201
  inputs.push(validated.value);
183
202
  }
184
203
  messages.push({ role: "assistant", content: calls.map(c => ({ type: "tool-call", ...c })) });
@@ -211,10 +230,13 @@ export class DecisionSession {
211
230
  metrics.modelCalls++;
212
231
  const result = streamText({ model: this.model, system: this.system, messages: [...messages], tools: this.modelTools,
213
232
  toolChoice: "required", maxRetries: 0, abortSignal: signal, maxOutputTokens: this.maxOutputTokens,
233
+ // The SDK default logs raw errors. Return allowlisted diagnostics instead.
234
+ onError: () => { },
214
235
  ...(this.providerOptions ? { providerOptions: this.providerOptions } : {}) });
215
236
  const iterator = result.fullStream[Symbol.asyncIterator]();
216
237
  const calls = [];
217
238
  let finish;
239
+ let invalidCall;
218
240
  let chars = 0;
219
241
  try {
220
242
  while (true) {
@@ -223,14 +245,20 @@ export class DecisionSession {
223
245
  if (next.done)
224
246
  break;
225
247
  const part = next.value;
226
- if (part.type === "error" || part.type === "tool-error")
227
- throw new DecisionFailure("invalid_output", "Provider returned an invalid response");
248
+ if (part.type === "error")
249
+ throw new DecisionFailure("invalid_output", "Provider request failed", providerDiagnostic(part.error));
250
+ if (part.type === "tool-error")
251
+ invalidCall ??= { code: "tool_input_invalid", stage: "validation" };
228
252
  if (part.type === "finish")
229
253
  finish = part.finishReason;
230
254
  if (part.type === "text-delta" || part.type === "reasoning-delta" || part.type === "tool-input-delta") {
231
255
  chars += part.type === "tool-input-delta" ? part.delta.length : part.text.length;
232
256
  }
233
257
  if (part.type === "tool-call") {
258
+ if ("invalid" in part && part.invalid) {
259
+ invalidCall = { code: Object.hasOwn(this.modelTools, part.toolName) ? "tool_input_invalid" : "unknown_tool", stage: "validation" };
260
+ continue;
261
+ }
234
262
  chars += boundedJson(part.input, this.limits.response).length;
235
263
  calls.push({ toolCallId: part.toolCallId, toolName: part.toolName, input: part.input });
236
264
  }
@@ -238,7 +266,9 @@ export class DecisionSession {
238
266
  throw new DecisionFailure("invalid_output", "Response budget exceeded");
239
267
  }
240
268
  if (finish !== "stop" && finish !== "tool-calls")
241
- throw new DecisionFailure("invalid_output", "Provider did not finish normally");
269
+ throw new DecisionFailure("invalid_output", "Provider did not finish normally", { code: finish === "length" ? "output_truncated" : "incomplete_response", stage: "protocol" });
270
+ if (invalidCall)
271
+ throw new DecisionFailure("invalid_output", "Invalid structured tool call", invalidCall);
242
272
  return calls;
243
273
  }
244
274
  finally {
package/dist/index.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import { type DeepCccProvider } from "./config.js";
7
7
  import { type PermissionMode, type PermissionResolver } from "./permissions.js";
8
8
  export { DecisionSession } from "./decision-session.js";
9
- export type { DecisionModelConfig, DecisionSessionOptions, DecisionRequest, DecisionResult, DecisionMetrics, DecisionFailureStatus, DecisionTool, DecisionToolContext } from "./decision-session.js";
9
+ export type { DecisionModelConfig, DecisionSessionOptions, DecisionRequest, DecisionResult, DecisionMetrics, DecisionFailureStatus, DecisionDiagnostic, DecisionTool, DecisionToolContext } from "./decision-session.js";
10
10
  export declare const DEFAULT_COMPACTION_TIMEOUT_MS: number;
11
11
  /**
12
12
  * 各操作系统特有的命令行指引(文本资产,维护在 os-prompts/ 目录,而非硬编码):
@@ -150,6 +150,16 @@ and tool call counts, and the last assembled context size. Provider/tool error
150
150
  payloads and partial generated content are not copied into result messages.
151
151
  Invalid configuration/request budgets throw before contacting the provider.
152
152
 
153
+ Since 0.2.23, failures also include a safe `diagnostic` object with a fixed `code`
154
+ and `stage`. Categories distinguish provider errors, invalid tool input, result
155
+ schema failures, truncated output, interrupted output, deadlines and cancellation.
156
+ Provider HTTP status is included when available, but error messages, raw tool
157
+ arguments, prompts, headers and response bodies are never copied into diagnostics.
158
+ The SDK's default raw error logging is disabled for this API. Integrations can
159
+ record their own schema issue paths inside their validators; keep values and raw
160
+ payloads out of those records. The original `status` remains the control-flow
161
+ contract. Diagnostics do not relax validation or trigger automatic retries.
162
+
153
163
  Pass an optional `AbortSignal` as the second argument to `decide()`. Provider SDK
154
164
  automatic retries are disabled; any future application retry needs a fresh,
155
165
  explicit budget. Non-cooperative asynchronous tools/streams are raced against
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepccc",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "A lightweight coding agent with OpenAI-compatible and Anthropic Messages API support.",
5
5
  "license": "Apache-2.0",
6
6
  "keywords": [