reasonix 0.0.2 → 0.0.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/README.md +12 -5
- package/dist/{chunk-XILYSYPT.js → chunk-Y7L6L5QS.js} +2 -1
- package/dist/chunk-Y7L6L5QS.js.map +1 -0
- package/dist/cli/{chunk-OSNTDDD6.js → chunk-T2ODXAJP.js} +2 -1
- package/dist/cli/chunk-T2ODXAJP.js.map +1 -0
- package/dist/cli/{client-OWZXRMOE.js → client-RIVGDOJP.js} +2 -2
- package/dist/cli/index.js +218 -17
- package/dist/cli/index.js.map +1 -1
- package/dist/{client-4JTJKRDV.js → client-KEA2D52Q.js} +2 -2
- package/dist/index.d.ts +47 -7
- package/dist/index.js +115 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-XILYSYPT.js.map +0 -1
- package/dist/cli/chunk-OSNTDDD6.js.map +0 -1
- /package/dist/cli/{client-OWZXRMOE.js.map → client-RIVGDOJP.js.map} +0 -0
- /package/dist/{client-4JTJKRDV.js.map → client-KEA2D52Q.js.map} +0 -0
package/README.md
CHANGED
|
@@ -37,9 +37,16 @@ structures the agent loop as `[Immutable Prefix] + [Append-Only Log] +
|
|
|
37
37
|
|
|
38
38
|
### 2. R1 Thought Harvesting
|
|
39
39
|
R1's `reasoning_content` contains a *plan*, not just trivia to display. Reasonix
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
pipes it through a cheap V3 call (~$0.0001 / turn) in JSON mode and extracts
|
|
41
|
+
a typed plan state:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
{ subgoals: string[], hypotheses: string[], uncertainties: string[], rejectedPaths: string[] }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Opt-in to keep default cost identical: `reasonix chat --harvest` or
|
|
48
|
+
`new CacheFirstLoop({ harvest: true })`. The TUI renders the harvested state
|
|
49
|
+
as a compact magenta block above the answer.
|
|
43
50
|
|
|
44
51
|
### 3. Tool-Call Repair
|
|
45
52
|
R1/V3 have known quirks — tool calls leaking into `<think>`, dropped arguments
|
|
@@ -93,8 +100,8 @@ reasonix version
|
|
|
93
100
|
|
|
94
101
|
## Status
|
|
95
102
|
|
|
96
|
-
Pre-alpha.
|
|
97
|
-
|
|
103
|
+
Pre-alpha. All three pillars ship working end-to-end as of v0.0.3.
|
|
104
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
98
105
|
|
|
99
106
|
## Non-goals
|
|
100
107
|
|
|
@@ -130,6 +130,7 @@ var DeepSeekClient = class {
|
|
|
130
130
|
if (opts.tools?.length) payload.tools = opts.tools;
|
|
131
131
|
if (opts.temperature !== void 0) payload.temperature = opts.temperature;
|
|
132
132
|
if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
|
|
133
|
+
if (opts.responseFormat) payload.response_format = opts.responseFormat;
|
|
133
134
|
return payload;
|
|
134
135
|
}
|
|
135
136
|
async chat(opts) {
|
|
@@ -258,4 +259,4 @@ export {
|
|
|
258
259
|
Usage,
|
|
259
260
|
DeepSeekClient
|
|
260
261
|
};
|
|
261
|
-
//# sourceMappingURL=chunk-
|
|
262
|
+
//# sourceMappingURL=chunk-Y7L6L5QS.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/client.ts","../src/retry.ts"],"sourcesContent":["import { type EventSourceMessage, createParser } from \"eventsource-parser\";\nimport { type RetryOptions, fetchWithRetry } from \"./retry.js\";\nimport type { ChatMessage, ChatRequestOptions, RawUsage, ToolCall, ToolSpec } from \"./types.js\";\n\nexport class Usage {\n constructor(\n public promptTokens = 0,\n public completionTokens = 0,\n public totalTokens = 0,\n public promptCacheHitTokens = 0,\n public promptCacheMissTokens = 0,\n ) {}\n\n get cacheHitRatio(): number {\n const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;\n return denom > 0 ? this.promptCacheHitTokens / denom : 0;\n }\n\n static fromApi(raw: RawUsage | undefined | null): Usage {\n const u = raw ?? {};\n return new Usage(\n u.prompt_tokens ?? 0,\n u.completion_tokens ?? 0,\n u.total_tokens ?? 0,\n u.prompt_cache_hit_tokens ?? 0,\n u.prompt_cache_miss_tokens ?? 0,\n );\n }\n}\n\nexport interface ChatResponse {\n content: string;\n reasoningContent: string | null;\n toolCalls: ToolCall[];\n usage: Usage;\n raw: unknown;\n}\n\nexport interface StreamChunk {\n contentDelta?: string;\n reasoningDelta?: string;\n toolCallDelta?: { index: number; id?: string; name?: string; argumentsDelta?: string };\n usage?: Usage;\n finishReason?: string;\n raw: any;\n}\n\nexport interface DeepSeekClientOptions {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n /** Retry configuration. Pass `{ maxAttempts: 1 }` to disable retries. */\n retry?: RetryOptions;\n}\n\nexport class DeepSeekClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly retry: RetryOptions;\n private readonly _fetch: typeof fetch;\n\n constructor(opts: DeepSeekClientOptions = {}) {\n const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient.\",\n );\n }\n this.apiKey = apiKey;\n this.baseUrl = (\n opts.baseUrl ??\n process.env.DEEPSEEK_BASE_URL ??\n \"https://api.deepseek.com\"\n ).replace(/\\/+$/, \"\");\n this.timeoutMs = opts.timeoutMs ?? 120_000;\n this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);\n this.retry = opts.retry ?? {};\n }\n\n private buildPayload(opts: ChatRequestOptions, stream: boolean) {\n const payload: Record<string, unknown> = {\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.tools?.length) payload.tools = opts.tools;\n if (opts.temperature !== undefined) payload.temperature = opts.temperature;\n if (opts.maxTokens !== undefined) payload.max_tokens = opts.maxTokens;\n if (opts.responseFormat) payload.response_format = opts.responseFormat;\n return payload;\n }\n\n async chat(opts: ChatRequestOptions): Promise<ChatResponse> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n try {\n const resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(this.buildPayload(opts, false)),\n signal,\n },\n { ...this.retry, signal },\n );\n if (!resp.ok) {\n throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);\n }\n const data: any = await resp.json();\n const choice = data.choices?.[0]?.message ?? {};\n return {\n content: choice.content ?? \"\",\n reasoningContent: choice.reasoning_content ?? null,\n toolCalls: choice.tool_calls ?? [],\n usage: Usage.fromApi(data.usage),\n raw: data,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n async *stream(opts: ChatRequestOptions): AsyncGenerator<StreamChunk> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n let resp: Response;\n try {\n // Only the initial fetch is retried. Once the server has started sending\n // the stream body we do NOT retry — a mid-stream retry would re-bill and\n // desync the session context.\n resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify(this.buildPayload(opts, true)),\n signal,\n },\n { ...this.retry, signal },\n );\n } catch (err) {\n clearTimeout(timer);\n throw err;\n }\n if (!resp.ok || !resp.body) {\n clearTimeout(timer);\n throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => \"\")}`);\n }\n\n const queue: StreamChunk[] = [];\n let done = false;\n const parser = createParser({\n onEvent: (ev: EventSourceMessage) => {\n if (!ev.data || ev.data === \"[DONE]\") {\n done = true;\n return;\n }\n try {\n const json = JSON.parse(ev.data);\n const delta = json.choices?.[0]?.delta ?? {};\n const finishReason = json.choices?.[0]?.finish_reason ?? undefined;\n const chunk: StreamChunk = { raw: json, finishReason };\n if (typeof delta.content === \"string\" && delta.content.length > 0) {\n chunk.contentDelta = delta.content;\n }\n if (typeof delta.reasoning_content === \"string\" && delta.reasoning_content.length > 0) {\n chunk.reasoningDelta = delta.reasoning_content;\n }\n if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {\n const tc = delta.tool_calls[0];\n chunk.toolCallDelta = {\n index: tc.index ?? 0,\n id: tc.id,\n name: tc.function?.name,\n argumentsDelta: tc.function?.arguments,\n };\n }\n if (json.usage) {\n chunk.usage = Usage.fromApi(json.usage);\n }\n queue.push(chunk);\n } catch {\n /* skip malformed sse frame */\n }\n },\n });\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n try {\n while (true) {\n if (queue.length > 0) {\n yield queue.shift()!;\n continue;\n }\n if (done) break;\n const { value, done: streamDone } = await reader.read();\n if (streamDone) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n while (queue.length > 0) yield queue.shift()!;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n }\n}\n\nexport type { ChatMessage, ToolCall, ToolSpec };\n","/**\n * Retry layer for DeepSeek API calls.\n *\n * Wraps a `fetch` function so that transient failures (rate limiting, server\n * overload, network blips) don't kill an agent session. We explicitly DO NOT\n * retry:\n * - 4xx client errors other than 408 / 429 (bad key, bad request, ...)\n * - aborted requests (user cancelled)\n * - mid-stream body read errors (retrying costs money AND would desync)\n *\n * Retrying is controlled by attempt count + exponential backoff with jitter.\n * If the server sends a `Retry-After` header we honor it (capped by\n * `maxBackoffMs` so a misconfigured upstream can't park us forever).\n */\n\nexport interface RetryOptions {\n /** Maximum total attempts (including the first). Default 4. */\n maxAttempts?: number;\n /** Initial backoff in ms. Doubles each retry, with jitter. Default 500. */\n initialBackoffMs?: number;\n /** Upper bound on any single backoff delay. Default 10000 (10s). */\n maxBackoffMs?: number;\n /** HTTP statuses to treat as retryable. Default [408, 429, 500, 502, 503, 504]. */\n retryableStatuses?: readonly number[];\n /** Abort signal; we do NOT retry once aborted. */\n signal?: AbortSignal;\n /** Telemetry hook — called before each wait. */\n onRetry?: (info: RetryInfo) => void;\n}\n\nexport interface RetryInfo {\n attempt: number;\n reason: string;\n waitMs: number;\n}\n\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504] as const;\n\nexport async function fetchWithRetry(\n fetchFn: typeof fetch,\n url: string,\n init: RequestInit,\n opts: RetryOptions = {},\n): Promise<Response> {\n const maxAttempts = opts.maxAttempts ?? 4;\n const initial = opts.initialBackoffMs ?? 500;\n const cap = opts.maxBackoffMs ?? 10_000;\n const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (opts.signal?.aborted) throw new Error(\"aborted\");\n\n try {\n const resp = await fetchFn(url, init);\n\n // Success or non-retryable failure: return as-is.\n if (resp.ok || !retryable.has(resp.status)) return resp;\n\n // Retryable but out of attempts: return the last response so the caller\n // can surface the status to the user.\n if (attempt === maxAttempts - 1) return resp;\n\n // Drain the body so the connection can be reused on the next attempt.\n await resp.text().catch(() => undefined);\n\n const waitMs = computeWait(attempt, initial, cap, resp.headers.get(\"Retry-After\"));\n opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });\n await sleep(waitMs, opts.signal);\n } catch (err) {\n lastError = err;\n // Respect explicit aborts — do not retry.\n if (isAbortError(err) || opts.signal?.aborted) throw err;\n if (attempt === maxAttempts - 1) throw err;\n\n const waitMs = computeWait(attempt, initial, cap, null);\n opts.onRetry?.({\n attempt: attempt + 1,\n reason: `network: ${messageOf(err)}`,\n waitMs,\n });\n await sleep(waitMs, opts.signal);\n }\n }\n\n throw lastError ?? new Error(\"fetchWithRetry: loop exited unexpectedly\");\n}\n\nfunction computeWait(\n attempt: number,\n initial: number,\n cap: number,\n retryAfter: string | null,\n): number {\n if (retryAfter) {\n const seconds = Number.parseFloat(retryAfter);\n if (Number.isFinite(seconds) && seconds > 0) {\n return Math.min(seconds * 1000, cap);\n }\n }\n const exp = initial * 2 ** attempt;\n // Jitter range [75%, 125%] to spread retries out when many clients hit 429 together.\n const jitter = exp * (0.75 + Math.random() * 0.5);\n return Math.min(Math.max(jitter, 0), cap);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n if (signal) {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new Error(\"aborted\"));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const name = (err as { name?: unknown }).name;\n return name === \"AbortError\";\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return \"unknown error\";\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -132,6 +132,7 @@ var DeepSeekClient = class {
|
|
|
132
132
|
if (opts.tools?.length) payload.tools = opts.tools;
|
|
133
133
|
if (opts.temperature !== void 0) payload.temperature = opts.temperature;
|
|
134
134
|
if (opts.maxTokens !== void 0) payload.max_tokens = opts.maxTokens;
|
|
135
|
+
if (opts.responseFormat) payload.response_format = opts.responseFormat;
|
|
135
136
|
return payload;
|
|
136
137
|
}
|
|
137
138
|
async chat(opts) {
|
|
@@ -259,4 +260,4 @@ export {
|
|
|
259
260
|
Usage,
|
|
260
261
|
DeepSeekClient
|
|
261
262
|
};
|
|
262
|
-
//# sourceMappingURL=chunk-
|
|
263
|
+
//# sourceMappingURL=chunk-T2ODXAJP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/client.ts","../../src/retry.ts"],"sourcesContent":["import { type EventSourceMessage, createParser } from \"eventsource-parser\";\nimport { type RetryOptions, fetchWithRetry } from \"./retry.js\";\nimport type { ChatMessage, ChatRequestOptions, RawUsage, ToolCall, ToolSpec } from \"./types.js\";\n\nexport class Usage {\n constructor(\n public promptTokens = 0,\n public completionTokens = 0,\n public totalTokens = 0,\n public promptCacheHitTokens = 0,\n public promptCacheMissTokens = 0,\n ) {}\n\n get cacheHitRatio(): number {\n const denom = this.promptCacheHitTokens + this.promptCacheMissTokens;\n return denom > 0 ? this.promptCacheHitTokens / denom : 0;\n }\n\n static fromApi(raw: RawUsage | undefined | null): Usage {\n const u = raw ?? {};\n return new Usage(\n u.prompt_tokens ?? 0,\n u.completion_tokens ?? 0,\n u.total_tokens ?? 0,\n u.prompt_cache_hit_tokens ?? 0,\n u.prompt_cache_miss_tokens ?? 0,\n );\n }\n}\n\nexport interface ChatResponse {\n content: string;\n reasoningContent: string | null;\n toolCalls: ToolCall[];\n usage: Usage;\n raw: unknown;\n}\n\nexport interface StreamChunk {\n contentDelta?: string;\n reasoningDelta?: string;\n toolCallDelta?: { index: number; id?: string; name?: string; argumentsDelta?: string };\n usage?: Usage;\n finishReason?: string;\n raw: any;\n}\n\nexport interface DeepSeekClientOptions {\n apiKey?: string;\n baseUrl?: string;\n timeoutMs?: number;\n fetch?: typeof fetch;\n /** Retry configuration. Pass `{ maxAttempts: 1 }` to disable retries. */\n retry?: RetryOptions;\n}\n\nexport class DeepSeekClient {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n readonly retry: RetryOptions;\n private readonly _fetch: typeof fetch;\n\n constructor(opts: DeepSeekClientOptions = {}) {\n const apiKey = opts.apiKey ?? process.env.DEEPSEEK_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"DEEPSEEK_API_KEY is not set. Put it in .env or pass apiKey to DeepSeekClient.\",\n );\n }\n this.apiKey = apiKey;\n this.baseUrl = (\n opts.baseUrl ??\n process.env.DEEPSEEK_BASE_URL ??\n \"https://api.deepseek.com\"\n ).replace(/\\/+$/, \"\");\n this.timeoutMs = opts.timeoutMs ?? 120_000;\n this._fetch = opts.fetch ?? globalThis.fetch.bind(globalThis);\n this.retry = opts.retry ?? {};\n }\n\n private buildPayload(opts: ChatRequestOptions, stream: boolean) {\n const payload: Record<string, unknown> = {\n model: opts.model,\n messages: opts.messages,\n stream,\n };\n if (opts.tools?.length) payload.tools = opts.tools;\n if (opts.temperature !== undefined) payload.temperature = opts.temperature;\n if (opts.maxTokens !== undefined) payload.max_tokens = opts.maxTokens;\n if (opts.responseFormat) payload.response_format = opts.responseFormat;\n return payload;\n }\n\n async chat(opts: ChatRequestOptions): Promise<ChatResponse> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n try {\n const resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(this.buildPayload(opts, false)),\n signal,\n },\n { ...this.retry, signal },\n );\n if (!resp.ok) {\n throw new Error(`DeepSeek ${resp.status}: ${await resp.text()}`);\n }\n const data: any = await resp.json();\n const choice = data.choices?.[0]?.message ?? {};\n return {\n content: choice.content ?? \"\",\n reasoningContent: choice.reasoning_content ?? null,\n toolCalls: choice.tool_calls ?? [],\n usage: Usage.fromApi(data.usage),\n raw: data,\n };\n } finally {\n clearTimeout(timer);\n }\n }\n\n async *stream(opts: ChatRequestOptions): AsyncGenerator<StreamChunk> {\n const ctrl = new AbortController();\n const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);\n const signal = opts.signal ?? ctrl.signal;\n\n let resp: Response;\n try {\n // Only the initial fetch is retried. Once the server has started sending\n // the stream body we do NOT retry — a mid-stream retry would re-bill and\n // desync the session context.\n resp = await fetchWithRetry(\n this._fetch,\n `${this.baseUrl}/chat/completions`,\n {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${this.apiKey}`,\n \"Content-Type\": \"application/json\",\n Accept: \"text/event-stream\",\n },\n body: JSON.stringify(this.buildPayload(opts, true)),\n signal,\n },\n { ...this.retry, signal },\n );\n } catch (err) {\n clearTimeout(timer);\n throw err;\n }\n if (!resp.ok || !resp.body) {\n clearTimeout(timer);\n throw new Error(`DeepSeek ${resp.status}: ${await resp.text().catch(() => \"\")}`);\n }\n\n const queue: StreamChunk[] = [];\n let done = false;\n const parser = createParser({\n onEvent: (ev: EventSourceMessage) => {\n if (!ev.data || ev.data === \"[DONE]\") {\n done = true;\n return;\n }\n try {\n const json = JSON.parse(ev.data);\n const delta = json.choices?.[0]?.delta ?? {};\n const finishReason = json.choices?.[0]?.finish_reason ?? undefined;\n const chunk: StreamChunk = { raw: json, finishReason };\n if (typeof delta.content === \"string\" && delta.content.length > 0) {\n chunk.contentDelta = delta.content;\n }\n if (typeof delta.reasoning_content === \"string\" && delta.reasoning_content.length > 0) {\n chunk.reasoningDelta = delta.reasoning_content;\n }\n if (Array.isArray(delta.tool_calls) && delta.tool_calls.length > 0) {\n const tc = delta.tool_calls[0];\n chunk.toolCallDelta = {\n index: tc.index ?? 0,\n id: tc.id,\n name: tc.function?.name,\n argumentsDelta: tc.function?.arguments,\n };\n }\n if (json.usage) {\n chunk.usage = Usage.fromApi(json.usage);\n }\n queue.push(chunk);\n } catch {\n /* skip malformed sse frame */\n }\n },\n });\n\n const reader = resp.body.getReader();\n const decoder = new TextDecoder();\n try {\n while (true) {\n if (queue.length > 0) {\n yield queue.shift()!;\n continue;\n }\n if (done) break;\n const { value, done: streamDone } = await reader.read();\n if (streamDone) break;\n parser.feed(decoder.decode(value, { stream: true }));\n }\n while (queue.length > 0) yield queue.shift()!;\n } finally {\n clearTimeout(timer);\n reader.releaseLock();\n }\n }\n}\n\nexport type { ChatMessage, ToolCall, ToolSpec };\n","/**\n * Retry layer for DeepSeek API calls.\n *\n * Wraps a `fetch` function so that transient failures (rate limiting, server\n * overload, network blips) don't kill an agent session. We explicitly DO NOT\n * retry:\n * - 4xx client errors other than 408 / 429 (bad key, bad request, ...)\n * - aborted requests (user cancelled)\n * - mid-stream body read errors (retrying costs money AND would desync)\n *\n * Retrying is controlled by attempt count + exponential backoff with jitter.\n * If the server sends a `Retry-After` header we honor it (capped by\n * `maxBackoffMs` so a misconfigured upstream can't park us forever).\n */\n\nexport interface RetryOptions {\n /** Maximum total attempts (including the first). Default 4. */\n maxAttempts?: number;\n /** Initial backoff in ms. Doubles each retry, with jitter. Default 500. */\n initialBackoffMs?: number;\n /** Upper bound on any single backoff delay. Default 10000 (10s). */\n maxBackoffMs?: number;\n /** HTTP statuses to treat as retryable. Default [408, 429, 500, 502, 503, 504]. */\n retryableStatuses?: readonly number[];\n /** Abort signal; we do NOT retry once aborted. */\n signal?: AbortSignal;\n /** Telemetry hook — called before each wait. */\n onRetry?: (info: RetryInfo) => void;\n}\n\nexport interface RetryInfo {\n attempt: number;\n reason: string;\n waitMs: number;\n}\n\nconst DEFAULT_RETRYABLE_STATUSES = [408, 429, 500, 502, 503, 504] as const;\n\nexport async function fetchWithRetry(\n fetchFn: typeof fetch,\n url: string,\n init: RequestInit,\n opts: RetryOptions = {},\n): Promise<Response> {\n const maxAttempts = opts.maxAttempts ?? 4;\n const initial = opts.initialBackoffMs ?? 500;\n const cap = opts.maxBackoffMs ?? 10_000;\n const retryable = new Set(opts.retryableStatuses ?? DEFAULT_RETRYABLE_STATUSES);\n\n let lastError: unknown;\n\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n if (opts.signal?.aborted) throw new Error(\"aborted\");\n\n try {\n const resp = await fetchFn(url, init);\n\n // Success or non-retryable failure: return as-is.\n if (resp.ok || !retryable.has(resp.status)) return resp;\n\n // Retryable but out of attempts: return the last response so the caller\n // can surface the status to the user.\n if (attempt === maxAttempts - 1) return resp;\n\n // Drain the body so the connection can be reused on the next attempt.\n await resp.text().catch(() => undefined);\n\n const waitMs = computeWait(attempt, initial, cap, resp.headers.get(\"Retry-After\"));\n opts.onRetry?.({ attempt: attempt + 1, reason: `http ${resp.status}`, waitMs });\n await sleep(waitMs, opts.signal);\n } catch (err) {\n lastError = err;\n // Respect explicit aborts — do not retry.\n if (isAbortError(err) || opts.signal?.aborted) throw err;\n if (attempt === maxAttempts - 1) throw err;\n\n const waitMs = computeWait(attempt, initial, cap, null);\n opts.onRetry?.({\n attempt: attempt + 1,\n reason: `network: ${messageOf(err)}`,\n waitMs,\n });\n await sleep(waitMs, opts.signal);\n }\n }\n\n throw lastError ?? new Error(\"fetchWithRetry: loop exited unexpectedly\");\n}\n\nfunction computeWait(\n attempt: number,\n initial: number,\n cap: number,\n retryAfter: string | null,\n): number {\n if (retryAfter) {\n const seconds = Number.parseFloat(retryAfter);\n if (Number.isFinite(seconds) && seconds > 0) {\n return Math.min(seconds * 1000, cap);\n }\n }\n const exp = initial * 2 ** attempt;\n // Jitter range [75%, 125%] to spread retries out when many clients hit 429 together.\n const jitter = exp * (0.75 + Math.random() * 0.5);\n return Math.min(Math.max(jitter, 0), cap);\n}\n\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve();\n return new Promise((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n if (signal) {\n const onAbort = () => {\n clearTimeout(timer);\n reject(new Error(\"aborted\"));\n };\n if (signal.aborted) onAbort();\n else signal.addEventListener(\"abort\", onAbort, { once: true });\n }\n });\n}\n\nfunction isAbortError(err: unknown): boolean {\n if (!err || typeof err !== \"object\") return false;\n const name = (err as { name?: unknown }).name;\n return name === \"AbortError\";\n}\n\nfunction messageOf(err: unknown): string {\n if (err instanceof Error) return err.message;\n try {\n return String(err);\n } catch {\n return \"unknown error\";\n }\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
DeepSeekClient
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-T2ODXAJP.js";
|
|
5
5
|
|
|
6
6
|
// src/cli/index.ts
|
|
7
7
|
import { Command } from "commander";
|
|
@@ -10,8 +10,90 @@ import { Command } from "commander";
|
|
|
10
10
|
function emptyPlanState() {
|
|
11
11
|
return { subgoals: [], hypotheses: [], uncertainties: [], rejectedPaths: [] };
|
|
12
12
|
}
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
function isPlanStateEmpty(s) {
|
|
14
|
+
if (!s) return true;
|
|
15
|
+
return s.subgoals.length === 0 && s.hypotheses.length === 0 && s.uncertainties.length === 0 && s.rejectedPaths.length === 0;
|
|
16
|
+
}
|
|
17
|
+
var SYSTEM_PROMPT = `You extract a typed plan state from a reasoning trace produced by another LLM.
|
|
18
|
+
Output ONLY a JSON object. No markdown, no prose, no backticks.
|
|
19
|
+
|
|
20
|
+
Schema:
|
|
21
|
+
{
|
|
22
|
+
"subgoals": string[], // concrete intermediate objectives the trace identifies
|
|
23
|
+
"hypotheses": string[], // candidate approaches or assumptions being weighed
|
|
24
|
+
"uncertainties": string[], // facts the trace flags as unclear / to verify
|
|
25
|
+
"rejectedPaths": string[] // approaches the trace considered and then abandoned
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
Constraints:
|
|
29
|
+
- Every field must be present. Use [] if not applicable.
|
|
30
|
+
- Each array has at most {maxItems} items.
|
|
31
|
+
- Each item is plain text, at most {maxItemLen} characters, no markdown.
|
|
32
|
+
- Write in the same language as the trace (Chinese in \u2192 Chinese out, etc.).
|
|
33
|
+
- Do not quote back the trace; write short, specific phrases.`;
|
|
34
|
+
async function harvest(reasoningContent, client, options = {}) {
|
|
35
|
+
if (!client || !reasoningContent) return emptyPlanState();
|
|
36
|
+
const minLen = options.minReasoningLen ?? 40;
|
|
37
|
+
const trimmed = reasoningContent.trim();
|
|
38
|
+
if (trimmed.length < minLen) return emptyPlanState();
|
|
39
|
+
const model = options.model ?? "deepseek-chat";
|
|
40
|
+
const maxItems = options.maxItems ?? 5;
|
|
41
|
+
const maxItemLen = options.maxItemLen ?? 80;
|
|
42
|
+
const system = SYSTEM_PROMPT.replace("{maxItems}", String(maxItems)).replace(
|
|
43
|
+
"{maxItemLen}",
|
|
44
|
+
String(maxItemLen)
|
|
45
|
+
);
|
|
46
|
+
try {
|
|
47
|
+
const resp = await client.chat({
|
|
48
|
+
model,
|
|
49
|
+
messages: [
|
|
50
|
+
{ role: "system", content: system },
|
|
51
|
+
{ role: "user", content: trimmed }
|
|
52
|
+
],
|
|
53
|
+
responseFormat: { type: "json_object" },
|
|
54
|
+
temperature: 0,
|
|
55
|
+
maxTokens: 600
|
|
56
|
+
});
|
|
57
|
+
return parsePlanState(resp.content, maxItems, maxItemLen);
|
|
58
|
+
} catch {
|
|
59
|
+
return emptyPlanState();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function parsePlanState(raw, maxItems, maxItemLen) {
|
|
63
|
+
const text = (raw ?? "").trim();
|
|
64
|
+
if (!text) return emptyPlanState();
|
|
65
|
+
let parsed;
|
|
66
|
+
try {
|
|
67
|
+
parsed = JSON.parse(text);
|
|
68
|
+
} catch {
|
|
69
|
+
const match = text.match(/\{[\s\S]*\}/);
|
|
70
|
+
if (!match) return emptyPlanState();
|
|
71
|
+
try {
|
|
72
|
+
parsed = JSON.parse(match[0]);
|
|
73
|
+
} catch {
|
|
74
|
+
return emptyPlanState();
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (!parsed || typeof parsed !== "object") return emptyPlanState();
|
|
78
|
+
const obj = parsed;
|
|
79
|
+
return {
|
|
80
|
+
subgoals: sanitizeArray(obj.subgoals, maxItems, maxItemLen),
|
|
81
|
+
hypotheses: sanitizeArray(obj.hypotheses, maxItems, maxItemLen),
|
|
82
|
+
uncertainties: sanitizeArray(obj.uncertainties, maxItems, maxItemLen),
|
|
83
|
+
rejectedPaths: sanitizeArray(obj.rejectedPaths ?? obj.rejected_paths, maxItems, maxItemLen)
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function sanitizeArray(raw, maxItems, maxItemLen) {
|
|
87
|
+
if (!Array.isArray(raw)) return [];
|
|
88
|
+
const out = [];
|
|
89
|
+
for (const item of raw) {
|
|
90
|
+
if (out.length >= maxItems) break;
|
|
91
|
+
if (typeof item !== "string") continue;
|
|
92
|
+
const cleaned = item.trim().replace(/\s+/g, " ");
|
|
93
|
+
if (!cleaned) continue;
|
|
94
|
+
out.push(cleaned.length <= maxItemLen ? cleaned : `${cleaned.slice(0, maxItemLen - 1)}\u2026`);
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
15
97
|
}
|
|
16
98
|
|
|
17
99
|
// src/memory.ts
|
|
@@ -265,6 +347,74 @@ function repairTruncatedJson(input) {
|
|
|
265
347
|
}
|
|
266
348
|
}
|
|
267
349
|
|
|
350
|
+
// src/repair/flatten.ts
|
|
351
|
+
function analyzeSchema(schema) {
|
|
352
|
+
if (!schema) return { shouldFlatten: false, leafCount: 0, maxDepth: 0 };
|
|
353
|
+
let leafCount = 0;
|
|
354
|
+
let maxDepth = 0;
|
|
355
|
+
walk(schema, 0, (depth, isLeaf) => {
|
|
356
|
+
if (isLeaf) leafCount++;
|
|
357
|
+
if (depth > maxDepth) maxDepth = depth;
|
|
358
|
+
});
|
|
359
|
+
return {
|
|
360
|
+
shouldFlatten: leafCount > 10 || maxDepth > 2,
|
|
361
|
+
leafCount,
|
|
362
|
+
maxDepth
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
function flattenSchema(schema) {
|
|
366
|
+
const flatProps = {};
|
|
367
|
+
const required = [];
|
|
368
|
+
collect("", schema, flatProps, required, true);
|
|
369
|
+
return {
|
|
370
|
+
type: "object",
|
|
371
|
+
properties: flatProps,
|
|
372
|
+
required
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
function nestArguments(flatArgs) {
|
|
376
|
+
const out = {};
|
|
377
|
+
for (const [key, value] of Object.entries(flatArgs)) {
|
|
378
|
+
setByPath(out, key.split("."), value);
|
|
379
|
+
}
|
|
380
|
+
return out;
|
|
381
|
+
}
|
|
382
|
+
function walk(schema, depth, visit) {
|
|
383
|
+
if (schema.type === "object" && schema.properties) {
|
|
384
|
+
for (const child of Object.values(schema.properties)) {
|
|
385
|
+
walk(child, depth + 1, visit);
|
|
386
|
+
}
|
|
387
|
+
return;
|
|
388
|
+
}
|
|
389
|
+
if (schema.type === "array" && schema.items) {
|
|
390
|
+
walk(schema.items, depth + 1, visit);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
visit(depth, true);
|
|
394
|
+
}
|
|
395
|
+
function collect(prefix, schema, out, required, isRootRequired) {
|
|
396
|
+
if (schema.type === "object" && schema.properties) {
|
|
397
|
+
const requiredSet = new Set(schema.required ?? []);
|
|
398
|
+
for (const [key, child] of Object.entries(schema.properties)) {
|
|
399
|
+
const nextPrefix = prefix ? `${prefix}.${key}` : key;
|
|
400
|
+
const childRequired = isRootRequired && requiredSet.has(key);
|
|
401
|
+
collect(nextPrefix, child, out, required, childRequired);
|
|
402
|
+
}
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
out[prefix] = schema;
|
|
406
|
+
if (isRootRequired) required.push(prefix);
|
|
407
|
+
}
|
|
408
|
+
function setByPath(target, path, value) {
|
|
409
|
+
let cur = target;
|
|
410
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
411
|
+
const key = path[i];
|
|
412
|
+
if (typeof cur[key] !== "object" || cur[key] === null) cur[key] = {};
|
|
413
|
+
cur = cur[key];
|
|
414
|
+
}
|
|
415
|
+
cur[path[path.length - 1]] = value;
|
|
416
|
+
}
|
|
417
|
+
|
|
268
418
|
// src/repair/index.ts
|
|
269
419
|
var ToolCallRepair = class {
|
|
270
420
|
storm;
|
|
@@ -386,9 +536,20 @@ function round(n, digits) {
|
|
|
386
536
|
// src/tools.ts
|
|
387
537
|
var ToolRegistry = class {
|
|
388
538
|
_tools = /* @__PURE__ */ new Map();
|
|
539
|
+
_autoFlatten;
|
|
540
|
+
constructor(opts = {}) {
|
|
541
|
+
this._autoFlatten = opts.autoFlatten !== false;
|
|
542
|
+
}
|
|
389
543
|
register(def) {
|
|
390
544
|
if (!def.name) throw new Error("tool requires a name");
|
|
391
|
-
|
|
545
|
+
const internal = { ...def };
|
|
546
|
+
if (this._autoFlatten && def.parameters) {
|
|
547
|
+
const decision = analyzeSchema(def.parameters);
|
|
548
|
+
if (decision.shouldFlatten) {
|
|
549
|
+
internal.flatSchema = flattenSchema(def.parameters);
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
this._tools.set(def.name, internal);
|
|
392
553
|
return this;
|
|
393
554
|
}
|
|
394
555
|
has(name) {
|
|
@@ -400,13 +561,17 @@ var ToolRegistry = class {
|
|
|
400
561
|
get size() {
|
|
401
562
|
return this._tools.size;
|
|
402
563
|
}
|
|
564
|
+
/** True if a registered tool's schema was flattened for the model. */
|
|
565
|
+
wasFlattened(name) {
|
|
566
|
+
return Boolean(this._tools.get(name)?.flatSchema);
|
|
567
|
+
}
|
|
403
568
|
specs() {
|
|
404
569
|
return [...this._tools.values()].map((t) => ({
|
|
405
570
|
type: "function",
|
|
406
571
|
function: {
|
|
407
572
|
name: t.name,
|
|
408
573
|
description: t.description ?? "",
|
|
409
|
-
parameters: t.parameters ?? { type: "object", properties: {} }
|
|
574
|
+
parameters: t.flatSchema ?? t.parameters ?? { type: "object", properties: {} }
|
|
410
575
|
}
|
|
411
576
|
}));
|
|
412
577
|
}
|
|
@@ -417,12 +582,15 @@ var ToolRegistry = class {
|
|
|
417
582
|
}
|
|
418
583
|
let args;
|
|
419
584
|
try {
|
|
420
|
-
args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) : {} : argumentsRaw ?? {};
|
|
585
|
+
args = typeof argumentsRaw === "string" ? argumentsRaw.trim() ? JSON.parse(argumentsRaw) ?? {} : {} : argumentsRaw ?? {};
|
|
421
586
|
} catch (err) {
|
|
422
587
|
return JSON.stringify({
|
|
423
588
|
error: `invalid tool arguments JSON: ${err.message}`
|
|
424
589
|
});
|
|
425
590
|
}
|
|
591
|
+
if (tool.flatSchema && args && typeof args === "object" && hasDotKey(args)) {
|
|
592
|
+
args = nestArguments(args);
|
|
593
|
+
}
|
|
426
594
|
try {
|
|
427
595
|
const result = await tool.fn(args);
|
|
428
596
|
return typeof result === "string" ? result : JSON.stringify(result);
|
|
@@ -433,6 +601,12 @@ var ToolRegistry = class {
|
|
|
433
601
|
}
|
|
434
602
|
}
|
|
435
603
|
};
|
|
604
|
+
function hasDotKey(obj) {
|
|
605
|
+
for (const k of Object.keys(obj)) {
|
|
606
|
+
if (k.includes(".")) return true;
|
|
607
|
+
}
|
|
608
|
+
return false;
|
|
609
|
+
}
|
|
436
610
|
|
|
437
611
|
// src/loop.ts
|
|
438
612
|
var CacheFirstLoop = class {
|
|
@@ -442,6 +616,8 @@ var CacheFirstLoop = class {
|
|
|
442
616
|
model;
|
|
443
617
|
maxToolIters;
|
|
444
618
|
stream;
|
|
619
|
+
harvestEnabled;
|
|
620
|
+
harvestOptions;
|
|
445
621
|
log = new AppendOnlyLog();
|
|
446
622
|
scratch = new VolatileScratch();
|
|
447
623
|
stats = new SessionStats();
|
|
@@ -454,6 +630,8 @@ var CacheFirstLoop = class {
|
|
|
454
630
|
this.model = opts.model ?? "deepseek-chat";
|
|
455
631
|
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
456
632
|
this.stream = opts.stream ?? true;
|
|
633
|
+
this.harvestEnabled = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
634
|
+
this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : {};
|
|
457
635
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
458
636
|
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
459
637
|
}
|
|
@@ -537,14 +715,14 @@ var CacheFirstLoop = class {
|
|
|
537
715
|
const turnStats = this.stats.record(
|
|
538
716
|
this._turn,
|
|
539
717
|
this.model,
|
|
540
|
-
usage ?? new (await import("./client-
|
|
718
|
+
usage ?? new (await import("./client-RIVGDOJP.js")).Usage()
|
|
541
719
|
);
|
|
542
720
|
if (pendingUser !== null) {
|
|
543
721
|
this.log.append({ role: "user", content: pendingUser });
|
|
544
722
|
pendingUser = null;
|
|
545
723
|
}
|
|
546
724
|
this.scratch.reasoning = reasoningContent || null;
|
|
547
|
-
const planState = await harvest(reasoningContent || null);
|
|
725
|
+
const planState = this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
548
726
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
549
727
|
toolCalls,
|
|
550
728
|
reasoningContent || null
|
|
@@ -828,7 +1006,7 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
828
1006
|
}
|
|
829
1007
|
if (event.role === "assistant") {
|
|
830
1008
|
if (event.streaming) return /* @__PURE__ */ React2.createElement(StreamingAssistant, { event });
|
|
831
|
-
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant")), event.reasoning ? /* @__PURE__ */ React2.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, event.text ? /* @__PURE__ */ React2.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React2.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React2.createElement(Text2, { color: "magenta" }, event.repair) : null);
|
|
1009
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, { bold: true, color: "green" }, "assistant")), event.reasoning ? /* @__PURE__ */ React2.createElement(ReasoningBlock, { reasoning: event.reasoning }) : null, !isPlanStateEmpty(event.planState) ? /* @__PURE__ */ React2.createElement(PlanStateBlock, { planState: event.planState }) : null, event.text ? /* @__PURE__ */ React2.createElement(Markdown, { text: event.text }) : /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, "(no content)"), event.stats ? /* @__PURE__ */ React2.createElement(StatsLine, { stats: event.stats }) : null, event.repair ? /* @__PURE__ */ React2.createElement(Text2, { color: "magenta" }, event.repair) : null);
|
|
832
1010
|
}
|
|
833
1011
|
if (event.role === "tool") {
|
|
834
1012
|
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React2.createElement(Text2, { color: "yellow" }, `tool<${event.toolName ?? "?"}> \u2192`), /* @__PURE__ */ React2.createElement(Text2, { dimColor: true }, " ", truncate(event.text, 400)));
|
|
@@ -841,6 +1019,14 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
841
1019
|
}
|
|
842
1020
|
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, event.text));
|
|
843
1021
|
});
|
|
1022
|
+
function PlanStateBlock({ planState }) {
|
|
1023
|
+
const lines = [];
|
|
1024
|
+
if (planState.subgoals.length) lines.push(["subgoals", planState.subgoals]);
|
|
1025
|
+
if (planState.hypotheses.length) lines.push(["hypotheses", planState.hypotheses]);
|
|
1026
|
+
if (planState.uncertainties.length) lines.push(["uncertainties", planState.uncertainties]);
|
|
1027
|
+
if (planState.rejectedPaths.length) lines.push(["rejected", planState.rejectedPaths]);
|
|
1028
|
+
return /* @__PURE__ */ React2.createElement(Box2, { flexDirection: "column", marginBottom: 1 }, lines.map(([label, items]) => /* @__PURE__ */ React2.createElement(Text2, { key: label, color: "magenta" }, "\u2039 ", /* @__PURE__ */ React2.createElement(Text2, { bold: true }, label), ` (${items.length}): ${items.join(" \xB7 ")}`)));
|
|
1029
|
+
}
|
|
844
1030
|
function ReasoningBlock({ reasoning }) {
|
|
845
1031
|
const max = 220;
|
|
846
1032
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
@@ -876,13 +1062,15 @@ function PromptInput({
|
|
|
876
1062
|
disabled,
|
|
877
1063
|
placeholder
|
|
878
1064
|
}) {
|
|
879
|
-
|
|
1065
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? 'type a message, or "/exit"';
|
|
1066
|
+
return /* @__PURE__ */ React3.createElement(Box3, { borderStyle: "round", borderColor: disabled ? "gray" : "cyan", paddingX: 1 }, /* @__PURE__ */ React3.createElement(Text3, { bold: true, color: disabled ? "gray" : "cyan" }, "you \u203A", " "), /* @__PURE__ */ React3.createElement(
|
|
880
1067
|
TextInput,
|
|
881
1068
|
{
|
|
882
1069
|
value,
|
|
883
1070
|
onChange,
|
|
884
1071
|
onSubmit,
|
|
885
|
-
|
|
1072
|
+
focus: !disabled,
|
|
1073
|
+
placeholder: effectivePlaceholder
|
|
886
1074
|
}
|
|
887
1075
|
));
|
|
888
1076
|
}
|
|
@@ -898,7 +1086,7 @@ function StatsPanel({ summary, model, prefixHash }) {
|
|
|
898
1086
|
|
|
899
1087
|
// src/cli/ui/App.tsx
|
|
900
1088
|
var FLUSH_INTERVAL_MS = 60;
|
|
901
|
-
function App({ model, system, transcript }) {
|
|
1089
|
+
function App({ model, system, transcript, harvest: harvest2 }) {
|
|
902
1090
|
const { exit } = useApp();
|
|
903
1091
|
const [historical, setHistorical] = useState([]);
|
|
904
1092
|
const [streaming, setStreaming] = useState(null);
|
|
@@ -925,10 +1113,10 @@ function App({ model, system, transcript }) {
|
|
|
925
1113
|
if (loopRef.current) return loopRef.current;
|
|
926
1114
|
const client = new DeepSeekClient();
|
|
927
1115
|
const prefix = new ImmutablePrefix({ system });
|
|
928
|
-
const l = new CacheFirstLoop({ client, prefix, model });
|
|
1116
|
+
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2 });
|
|
929
1117
|
loopRef.current = l;
|
|
930
1118
|
return l;
|
|
931
|
-
}, [model, system]);
|
|
1119
|
+
}, [model, system, harvest2]);
|
|
932
1120
|
const prefixHash = loop.prefix.fingerprint;
|
|
933
1121
|
const writeTranscript = useCallback((ev) => {
|
|
934
1122
|
transcriptRef.current?.write(
|
|
@@ -995,6 +1183,7 @@ function App({ model, system, transcript }) {
|
|
|
995
1183
|
role: "assistant",
|
|
996
1184
|
text: ev.content || streamRef.text,
|
|
997
1185
|
reasoning: streamRef.reasoning || void 0,
|
|
1186
|
+
planState: ev.planState,
|
|
998
1187
|
stats: ev.stats,
|
|
999
1188
|
repair: repairNote || void 0,
|
|
1000
1189
|
streaming: false
|
|
@@ -1092,7 +1281,15 @@ function Root({ initialKey, ...appProps }) {
|
|
|
1092
1281
|
);
|
|
1093
1282
|
}
|
|
1094
1283
|
process.env.DEEPSEEK_API_KEY = key;
|
|
1095
|
-
return /* @__PURE__ */ React7.createElement(
|
|
1284
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1285
|
+
App,
|
|
1286
|
+
{
|
|
1287
|
+
model: appProps.model,
|
|
1288
|
+
system: appProps.system,
|
|
1289
|
+
transcript: appProps.transcript,
|
|
1290
|
+
harvest: appProps.harvest
|
|
1291
|
+
}
|
|
1292
|
+
);
|
|
1096
1293
|
}
|
|
1097
1294
|
async function chatCommand(opts) {
|
|
1098
1295
|
loadDotenv();
|
|
@@ -1197,11 +1394,15 @@ function versionCommand() {
|
|
|
1197
1394
|
var DEFAULT_SYSTEM = "You are Reasonix, a helpful DeepSeek-powered assistant. Be concise and accurate. Use tools when available.";
|
|
1198
1395
|
var program = new Command();
|
|
1199
1396
|
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION);
|
|
1200
|
-
program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").
|
|
1397
|
+
program.command("chat").description("Interactive Ink TUI with live cache/cost panel.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt (pinned in the immutable prefix)", DEFAULT_SYSTEM).option("--transcript <path>", "Write a JSONL transcript to this path").option(
|
|
1398
|
+
"--harvest",
|
|
1399
|
+
"Extract typed plan state from R1 reasoning (Pillar 2, adds a cheap V3 call per turn)"
|
|
1400
|
+
).action(async (opts) => {
|
|
1201
1401
|
await chatCommand({
|
|
1202
1402
|
model: opts.model,
|
|
1203
1403
|
system: opts.system,
|
|
1204
|
-
transcript: opts.transcript
|
|
1404
|
+
transcript: opts.transcript,
|
|
1405
|
+
harvest: !!opts.harvest
|
|
1205
1406
|
});
|
|
1206
1407
|
});
|
|
1207
1408
|
program.command("run <task>").description("Run a single task non-interactively, streaming output.").option("-m, --model <id>", "DeepSeek model id", "deepseek-chat").option("-s, --system <prompt>", "System prompt", DEFAULT_SYSTEM).action(async (task, opts) => {
|