@askdkc/dsh-cli 0.13.1 → 0.13.2

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.
@@ -1,10 +1,12 @@
1
1
  /** Convert DSH conversation blocks into low-level SDK prompts without an agent loop. */
2
2
  import { LlmError, projectToolUpdates, fileHandleText } from '@deepseek-ai/dsh-llm';
3
3
  import { projectReplay, requireReplaySignature } from './opencode-replay.js';
4
+ import { createHash } from 'node:crypto';
4
5
  export async function encodePrompt(options, model, attachments, warn) {
5
6
  const prompt = [];
6
7
  const names = new Map();
7
8
  const callIds = new Map();
9
+ const wireIds = new Set();
8
10
  if (options.system)
9
11
  prompt.push({ role: 'system', content: options.system });
10
12
  const projection = projectToolUpdates(options.messages, options.tools, undefined, options.toolHistory);
@@ -61,14 +63,24 @@ export async function encodePrompt(options, model, attachments, warn) {
61
63
  else if (block.type === 'file')
62
64
  content.push({ type: 'text', text: fileHandleText(block.attachment, attachments?.()?.fileHostPath(block.attachment)) });
63
65
  else if (block.type === 'reasoning' && message.role === 'assistant') {
66
+ if (replay.foreign)
67
+ continue;
64
68
  requireReplaySignature(block, model.protocol, providerOptions);
65
69
  content.push({ type: 'reasoning', text: block.text, providerOptions });
66
70
  }
67
71
  else if (block.type === 'tool-call' && message.role === 'assistant') {
68
- if (model.protocol === 'responses' && block.id.includes('|') && !replay.callIds.has(block.id))
72
+ // Foreign pi Responses ids contain an opaque item suffix. Give the
73
+ // call/result pair a portable id without restoring that foreign item.
74
+ if (replay.foreign && model.protocol === 'responses' && block.id.includes('|'))
75
+ callIds.set(block.id, `dsh_${createHash('sha256').update(block.id).digest('hex').slice(0, 48)}`);
76
+ if (model.protocol === 'responses' && block.id.includes('|') && !callIds.has(block.id))
69
77
  throw new LlmError('Cannot restore legacy Responses tool call pairing', 'INVALID_REPLAY_STATE');
70
78
  if (names.has(block.id))
71
79
  throw new LlmError('Duplicate stored tool call id', 'INVALID_REPLAY_STATE');
80
+ const wireId = callIds.get(block.id) ?? block.id;
81
+ if (wireIds.has(wireId))
82
+ throw new LlmError('Duplicate wire tool call id', 'INVALID_REPLAY_STATE');
83
+ wireIds.add(wireId);
72
84
  if (model.protocol === 'google' && model.reasoning && !providerOptions?.google?.thoughtSignature && !message.content.slice(0, index).some(part => part.type === 'tool-call'))
73
85
  throw new LlmError('Cannot replay Google tool call without its verified thought signature', 'INVALID_REPLAY_STATE');
74
86
  names.set(block.id, block.name);
@@ -79,7 +91,7 @@ export async function encodePrompt(options, model, attachments, warn) {
79
91
  catch {
80
92
  throw new LlmError('Stored tool arguments are not valid JSON', 'INVALID_REPLAY_STATE');
81
93
  }
82
- content.push({ type: 'tool-call', toolCallId: callIds.get(block.id) ?? block.id, toolName: block.name, input, providerOptions });
94
+ content.push({ type: 'tool-call', toolCallId: wireId, toolName: block.name, input, providerOptions });
83
95
  }
84
96
  else if (block.type === 'image') {
85
97
  if (message.role === 'assistant')
@@ -87,7 +99,8 @@ export async function encodePrompt(options, model, attachments, warn) {
87
99
  content.push(await imagePart(block));
88
100
  }
89
101
  }
90
- prompt.push({ role: message.role, content });
102
+ if (content.length || !replay.foreign)
103
+ prompt.push({ role: message.role, content });
91
104
  }
92
105
  return prompt;
93
106
  }
@@ -8,6 +8,7 @@ export interface ReplayProjection {
8
8
  blocks: (SharedV4ProviderOptions | undefined)[];
9
9
  callIds: Map<string, string>;
10
10
  trusted: boolean;
11
+ foreign: boolean;
11
12
  }
12
13
  /** Validate the entire aligned envelope before restoring any signature or item id. */
13
14
  export declare function projectReplay(source: unknown, content: readonly ContentBlock[], model: OpenCodeModel, route: string, warn?: (reason: string) => void): ReplayProjection;
@@ -33,9 +33,14 @@ function completeResponseSignatures(blocks, content) {
33
33
  const APIS = { messages: 'anthropic-messages', responses: 'openai-responses', chat: 'openai-completions', google: 'google-generative-ai' };
34
34
  /** Validate the entire aligned envelope before restoring any signature or item id. */
35
35
  export function projectReplay(source, content, model, route, warn) {
36
- const empty = () => ({ blocks: content.map(() => undefined), callIds: new Map(), trusted: false });
37
36
  const message = record(source);
38
37
  const envelope = record(message?.replayState);
38
+ // A model switch deliberately discards provider-specific replay data. It is
39
+ // distinct from a damaged native envelope, whose signature checks still apply.
40
+ const foreign = typeof message?.provider === 'string' && !!message.provider && typeof message.model === 'string' && !!message.model && (message.provider !== route || message.model !== model.id);
41
+ const empty = () => ({ blocks: content.map(() => undefined), callIds: new Map(), trusted: false, foreign });
42
+ if (foreign)
43
+ return empty();
39
44
  if (!envelope) {
40
45
  if (message?.replayState !== undefined)
41
46
  warn?.('OpenCode history: malformed replay envelope; retaining message content');
@@ -122,7 +127,7 @@ export function projectReplay(source, content, model, route, warn) {
122
127
  });
123
128
  if (model.protocol === 'responses')
124
129
  completeResponseSignatures(blocks, content);
125
- return { blocks, callIds, trusted: true };
130
+ return { blocks, callIds, trusted: true, foreign: false };
126
131
  }
127
132
  catch (error) {
128
133
  warn?.(`OpenCode history: ${error instanceof Error ? error.message : 'invalid replay'}; retaining message content`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askdkc/dsh-cli",
3
- "version": "0.13.1",
3
+ "version": "0.13.2",
4
4
  "description": "Interactive terminal interface for DeepSeek Harness agents, sessions and tools.",
5
5
  "keywords": [
6
6
  "dsh",