@khorsheed/dsh-local-agent-claude-code 0.2.0-rc.1 → 0.2.0

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.
@@ -14,7 +14,7 @@
14
14
  * loud, by design).
15
15
  * @module @khorsheed/dsh-local-agent-claude-code/claude-cli-provider
16
16
  */
17
- import type { ContentBlock, TokenUsage } from '@deepseek-ai/dsh-llm';
17
+ import type { ContentBlock, ReasoningEffortId, TokenUsage } from '@deepseek-ai/dsh-llm';
18
18
  import type { TodoItem } from '@deepseek-ai/dsh-tool-todo';
19
19
  import { type ResolvedSubagentStartRequest, type SubagentCapabilities, type SubagentProvider, type SubagentRun, type SubagentStartRequest, type SubagentStopReason } from '@deepseek-ai/dsh-subagent';
20
20
  import type { Context } from '@deepseek-ai/cordis';
@@ -22,14 +22,31 @@ import type { Session } from '@deepseek-ai/dsh-session';
22
22
  import type { SubprocessHandle, SubprocessSpawnSpec } from '@deepseek-ai/dsh-subprocess';
23
23
  import type { DelegationExecTarget, LocalAgentToolCalls } from '@khorsheed/dsh-local-agent/types';
24
24
  import type { ClaudeLiveDriver } from './live-driver.ts';
25
+ import type { ClaudeEndpointAuth } from './endpoint-auth.ts';
26
+ /**
27
+ * The synthesized result for a call whose result the stream never recorded
28
+ * (a settle-pass fold after a kill or a truncated stream). The released-log
29
+ * contract resolves every advertised call before its step closes, so an
30
+ * interrupted call folds with this error result. Shared verbatim with the
31
+ * kimi provider (`KIMI_INTERRUPTED_TOOL_RESULT`) so the family reads one text.
32
+ */
33
+ export declare const CLAUDE_INTERRUPTED_TOOL_RESULT = "\uFF08\u5DE5\u5177\u8C03\u7528\u88AB\u4E2D\u65AD\uFF0C\u672A\u80FD\u8BB0\u5F55\u8FD4\u56DE\u7ED3\u679C\uFF09";
25
34
  /** Default POSIX grace between subprocess termination tiers. */
26
35
  export declare const DEFAULT_DISPOSE_GRACE_MS = 3000;
27
36
  /**
28
37
  * Auth-shaped failure signatures in claude's result error or stderr: the
29
- * endpoint rejected the credential (401/403, revoked token). Narrow on
30
- * purpose — matched against provider-controlled error strings only.
38
+ * endpoint rejected the credential (401/403, revoked key). Matches the CLI's
39
+ * own phrasing ("Failed to authenticate. API Error: 401 …", the
40
+ * `authentication_failed` error tag) plus the relay phrasings seen in the
41
+ * wild ("invalid x-api-key" and friends). Narrow on purpose — matched against
42
+ * provider-controlled error strings only.
31
43
  */
32
44
  export declare const CLAUDE_AUTH_FAILURE: RegExp;
45
+ /**
46
+ * The MCP tool-call bound for a member's claude process (`MCP_TOOL_TIMEOUT`,
47
+ * ms): a bridge call may wait up to 30 minutes for the human's answer.
48
+ */
49
+ export declare const MEMBER_TOOL_TIMEOUT_MS: string;
33
50
  /**
34
51
  * Member channel registration for one claude process lifetime: mint the
35
52
  * per-run token and build the bridge MCP declaration for the spawn argv
@@ -62,7 +79,7 @@ export declare function claudeCliVersion(ctx: Context, homeDir: string): Promise
62
79
  export declare class ClaudeCliProvider implements SubagentProvider {
63
80
  private readonly ctx;
64
81
  private readonly permissionMode;
65
- private readonly baseUrl?;
82
+ private readonly endpoint?;
66
83
  private readonly live?;
67
84
  private readonly model?;
68
85
  readonly name = "claude-local";
@@ -73,6 +90,11 @@ export declare class ClaudeCliProvider implements SubagentProvider {
73
90
  * generation's driver per member (the settings toggle swaps generations;
74
91
  * a resolver may return undefined to steer one member's round to exec
75
92
  * while a retiring generation still hosts it).
93
+ * @param endpoint - resolver for the round's endpoint credential (endpoint-auth.ts:
94
+ * the scope's endpoint-auth.json over the plugin config triple), read PER
95
+ * ROUND against the round's scoped home, so a key rotation or a profile
96
+ * edit takes effect on the next delegation without a reload. Absent, no
97
+ * `ANTHROPIC_*` variables reach the child at all.
76
98
  * @param model - resolver for the member's effective model, read PER ROUND
77
99
  * so a settings-card write or a composer switch takes effect on the next
78
100
  * delegation without a reload. Receives the member (the child session id)
@@ -81,7 +103,7 @@ export declare class ClaudeCliProvider implements SubagentProvider {
81
103
  * resolver not passed), the delegation's own model still rides, exactly
82
104
  * as before the key existed.
83
105
  */
84
- constructor(ctx: Context, permissionMode?: 'skip' | 'normal', baseUrl?: string | undefined, live?: ClaudeLiveDriver | ((childSessionId: string) => ClaudeLiveDriver | undefined) | undefined, model?: ((childSessionId: string, delegationModel?: string) => string | undefined) | undefined);
106
+ constructor(ctx: Context, permissionMode?: 'skip' | 'normal', endpoint?: ((homeDir: string) => Promise<ClaudeEndpointAuth>) | undefined, live?: ClaudeLiveDriver | ((childSessionId: string) => ClaudeLiveDriver | undefined) | undefined, model?: ((childSessionId: string, delegationModel?: string) => string | undefined) | undefined);
85
107
  /** Resolve the live driver for one round's member, if live is on for it. */
86
108
  private liveDriver;
87
109
  /** The round's model fragment for the exec spawn spec (see {@link resolveRoundModel}). */
@@ -202,16 +224,25 @@ export type ClaudeTranscriptLine = {
202
224
  kind: 'think';
203
225
  text: string;
204
226
  streamId?: string;
227
+ messageId?: string;
228
+ usage?: TokenUsage;
205
229
  } | {
206
230
  kind: 'text';
207
231
  text: string;
208
232
  streamId?: string;
233
+ messageId?: string;
234
+ usage?: TokenUsage;
209
235
  }
210
236
  /**
211
237
  * Tool activity: one `tool_use` with its (possibly still pending)
212
238
  * `tool_result`. `id` is the stream's tool_use id when present, else a
213
239
  * synthesized position-based id — stable within a run either way, so the
214
- * child session's `tool/call`/`tool/result` events pair by it.
240
+ * child session's `tool/call`/`tool/result` events pair by it. `usage` is
241
+ * the API call's own accounting (claude reports it inline on every
242
+ * assistant event): it rides the call's LAST folded line — a tool-only
243
+ * call's lands on the tool line's advertising message. `messageId` groups
244
+ * the per-block events of ONE API call (claude emits one assistant event
245
+ * per content block, each repeating the message's usage).
215
246
  */
216
247
  | {
217
248
  kind: 'tool';
@@ -219,12 +250,21 @@ export type ClaudeTranscriptLine = {
219
250
  name: string;
220
251
  args?: string;
221
252
  result?: string;
253
+ messageId?: string;
254
+ usage?: TokenUsage;
222
255
  };
256
+ /** Group the lines of one API call (consecutive lines sharing a message id).
257
+ * Lines without an id stay singleton units (the pre-grouping shape). */
258
+ export declare function groupClaudeTranscriptLines(lines: readonly ClaudeTranscriptLine[]): ClaudeTranscriptLine[][];
223
259
  /** Mutable fold state shared by the batch parse and the incremental parser. */
224
260
  interface ClaudeStreamFoldState {
225
261
  readonly lines: ClaudeTranscriptLine[];
226
262
  /** tool_use id → transcript line index, so a result pairs with its own call. */
227
263
  readonly callsById: Map<string, number>;
264
+ /** message id → the line index its usage currently rides (per-call dedupe:
265
+ * claude repeats one call's usage on EVERY per-block assistant event, and
266
+ * only the call's last line may keep it). */
267
+ readonly usageStamped: Map<string, number>;
228
268
  text: string | undefined;
229
269
  usage: TokenUsage | undefined;
230
270
  sessionId: string | undefined;
@@ -302,6 +342,7 @@ export declare class ClaudeStreamParser implements ClaudeStreamFoldState {
302
342
  private buffer;
303
343
  readonly lines: ClaudeTranscriptLine[];
304
344
  readonly callsById: Map<string, number>;
345
+ readonly usageStamped: Map<string, number>;
305
346
  text: string | undefined;
306
347
  usage: TokenUsage | undefined;
307
348
  sessionId: string | undefined;
@@ -347,6 +388,17 @@ export declare function parseClaudeStreamJson(output: string): {
347
388
  };
348
389
  /** The placeholder think line for a `redacted_thinking` block (content is server-encrypted by design). */
349
390
  export declare const REDACTED_THINKING_TEXT = "\uFF08\u601D\u8003\u5185\u5BB9\u5DF2\u7531\u670D\u52A1\u7AEF\u9690\u85CF\uFF1Aredacted_thinking\uFF0C\u5185\u5BB9\u6309\u8BBE\u8BA1\u4E0D\u53EF\u89C1\uFF0C\u6B64\u884C\u4EC5\u6807\u793A\u5B83\u5B58\u5728\uFF09";
391
+ /**
392
+ * Map claude's usage payload onto the shared usage contract. Claude's
393
+ * `input_tokens` is the uncached input; cache reads and cache creation are
394
+ * reported separately, so each maps to its own bucket with no subtraction.
395
+ * When the payload carries ANY cache field, `cacheReadTokens` is always
396
+ * written (0 when the read counter is absent or zero) — a reader can then
397
+ * tell "no hits" from "not reported".
398
+ * @param usage - the raw claude usage object.
399
+ * @returns the shared usage record.
400
+ */
401
+ export declare function usageFromClaude(usage: unknown): TokenUsage;
350
402
  /**
351
403
  * Start the real `claude -p` child and publish its run. The claude
352
404
  * result arrives as an NDJSON event stream on stdout (`--verbose
@@ -362,6 +414,17 @@ export declare const REDACTED_THINKING_TEXT = "\uFF08\u601D\u8003\u5185\u5BB9\u5
362
414
  */
363
415
  export declare function startClaudeCliRun(request: SubagentStartRequest, spec: ClaudeCliRunSpec): Promise<SubagentRun>;
364
416
  /** One assistant-role message event, attributed to the claude route. */
417
+ /**
418
+ * Write the round's request/header when the config differs from the child
419
+ * log's last one (the host logs changed snapshots, not per-call duplicates):
420
+ * the first write is `initial`, a differing one `change`, an identical one
421
+ * skipped. Context Lens reads the child log's config from these headers.
422
+ */
423
+ export declare function appendClaudeRequestHeader(childSession: Session, config: {
424
+ provider: string;
425
+ model: string;
426
+ reasoningEffort?: ReasoningEffortId;
427
+ }): void;
365
428
  export declare function assistantEvent(blocks: readonly ContentBlock[]): import("@deepseek-ai/dsh-llm").AssistantMessage;
366
429
  /**
367
430
  * Fold one transcript line into the child session as one assistant step,
@@ -371,10 +434,27 @@ export declare function assistantEvent(blocks: readonly ContentBlock[]): import(
371
434
  * the real-time view renders nothing (only a full rebuild, which mints step
372
435
  * drafts from the explicit coordinates, recovers it). Tool cards escape that
373
436
  * fate only because their definition does not consult the step location.
437
+ *
438
+ * A tool line folds the native lifecycle inside its one step: an assistant
439
+ * message advertising the tool-call block, the `tool/call`, and its
440
+ * `tool/result` — the released-log reader pairs call and result against the
441
+ * advertisement and resolves every advertised call before the step closes, so
442
+ * a line whose result the stream has not recorded yet must NOT reach this
443
+ * fold mid-round (the callers hold there; the result merges into the call's
444
+ * line in place, so a later pass sees it). A line still unresulted at settle
445
+ * folds with the interrupted placeholder ({@link CLAUDE_INTERRUPTED_TOOL_RESULT}).
374
446
  */
375
447
  export declare function appendClaudeTranscriptLine(childSession: Session, turn: number, step: number, line: ClaudeTranscriptLine, usage: TokenUsage | undefined): void;
376
448
  /** The delta-progress text for one transcript line. */
377
449
  export declare function claudeLineText(line: ClaudeTranscriptLine): string;
450
+ /**
451
+ * Fold one API call (a message-id group) into the child session as ONE
452
+ * assistant step: a single assistant/message carrying the call's reasoning,
453
+ * text and tool-call blocks (the host's own mixed-content shape), then each
454
+ * call's native lifecycle, then step/end. The call's usage rides the message
455
+ * once — the group's own per-call stamp wins over the round-total fallback.
456
+ */
457
+ export declare function appendClaudeTranscriptGroup(childSession: Session, turn: number, step: number, group: readonly ClaudeTranscriptLine[], usage: TokenUsage | undefined): void;
378
458
  /**
379
459
  * Persist the child session's mirrored events. Thin wrapper over the core's
380
460
  * {@link persistChildSession}: a live session syncs through the core's cached