reasonix 0.0.1 → 0.0.3
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 +19 -8
- 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 +262 -24
- package/dist/cli/index.js.map +1 -1
- package/dist/{client-4JTJKRDV.js → client-KEA2D52Q.js} +2 -2
- package/dist/index.d.ts +60 -7
- package/dist/index.js +139 -6
- 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
|
@@ -8,11 +8,15 @@ automatic prefix caching — and turn them into agent-loop superpowers that gene
|
|
|
8
8
|
frameworks leave on the table.
|
|
9
9
|
|
|
10
10
|
```bash
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
npx reasonix chat # live TUI with real-time cache-hit and cost panel
|
|
11
|
+
npx reasonix chat # prompts for your DeepSeek key on first run,
|
|
12
|
+
# then live TUI with real-time cache/cost panel
|
|
14
13
|
```
|
|
15
14
|
|
|
15
|
+
On first run the TUI asks for your DeepSeek API key (get one at
|
|
16
|
+
[platform.deepseek.com/api_keys](https://platform.deepseek.com/api_keys)) and
|
|
17
|
+
saves it to `~/.reasonix/config.json`. Set `DEEPSEEK_API_KEY` in the
|
|
18
|
+
environment to override.
|
|
19
|
+
|
|
16
20
|
## Why Reasonix?
|
|
17
21
|
|
|
18
22
|
Every other framework treats DeepSeek as an OpenAI-compatible endpoint with a
|
|
@@ -33,9 +37,16 @@ structures the agent loop as `[Immutable Prefix] + [Append-Only Log] +
|
|
|
33
37
|
|
|
34
38
|
### 2. R1 Thought Harvesting
|
|
35
39
|
R1's `reasoning_content` contains a *plan*, not just trivia to display. Reasonix
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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.
|
|
39
50
|
|
|
40
51
|
### 3. Tool-Call Repair
|
|
41
52
|
R1/V3 have known quirks — tool calls leaking into `<think>`, dropped arguments
|
|
@@ -89,8 +100,8 @@ reasonix version
|
|
|
89
100
|
|
|
90
101
|
## Status
|
|
91
102
|
|
|
92
|
-
Pre-alpha.
|
|
93
|
-
|
|
103
|
+
Pre-alpha. All three pillars ship working end-to-end as of v0.0.3.
|
|
104
|
+
See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md).
|
|
94
105
|
|
|
95
106
|
## Non-goals
|
|
96
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
|
|
@@ -442,6 +524,8 @@ var CacheFirstLoop = class {
|
|
|
442
524
|
model;
|
|
443
525
|
maxToolIters;
|
|
444
526
|
stream;
|
|
527
|
+
harvestEnabled;
|
|
528
|
+
harvestOptions;
|
|
445
529
|
log = new AppendOnlyLog();
|
|
446
530
|
scratch = new VolatileScratch();
|
|
447
531
|
stats = new SessionStats();
|
|
@@ -454,6 +538,8 @@ var CacheFirstLoop = class {
|
|
|
454
538
|
this.model = opts.model ?? "deepseek-chat";
|
|
455
539
|
this.maxToolIters = opts.maxToolIters ?? 8;
|
|
456
540
|
this.stream = opts.stream ?? true;
|
|
541
|
+
this.harvestEnabled = opts.harvest === true || typeof opts.harvest === "object" && opts.harvest !== null;
|
|
542
|
+
this.harvestOptions = typeof opts.harvest === "object" && opts.harvest !== null ? opts.harvest : {};
|
|
457
543
|
const allowedNames = /* @__PURE__ */ new Set([...this.prefix.toolSpecs.map((s) => s.function.name)]);
|
|
458
544
|
this.repair = new ToolCallRepair({ allowedToolNames: allowedNames });
|
|
459
545
|
}
|
|
@@ -537,14 +623,14 @@ var CacheFirstLoop = class {
|
|
|
537
623
|
const turnStats = this.stats.record(
|
|
538
624
|
this._turn,
|
|
539
625
|
this.model,
|
|
540
|
-
usage ?? new (await import("./client-
|
|
626
|
+
usage ?? new (await import("./client-RIVGDOJP.js")).Usage()
|
|
541
627
|
);
|
|
542
628
|
if (pendingUser !== null) {
|
|
543
629
|
this.log.append({ role: "user", content: pendingUser });
|
|
544
630
|
pendingUser = null;
|
|
545
631
|
}
|
|
546
632
|
this.scratch.reasoning = reasoningContent || null;
|
|
547
|
-
const planState = await harvest(reasoningContent || null);
|
|
633
|
+
const planState = this.harvestEnabled ? await harvest(reasoningContent || null, this.client, this.harvestOptions) : emptyPlanState();
|
|
548
634
|
const { calls: repairedCalls, report } = this.repair.process(
|
|
549
635
|
toolCalls,
|
|
550
636
|
reasoningContent || null
|
|
@@ -617,12 +703,55 @@ function loadDotenv(path = ".env") {
|
|
|
617
703
|
}
|
|
618
704
|
}
|
|
619
705
|
|
|
706
|
+
// src/config.ts
|
|
707
|
+
import { chmodSync, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
|
|
708
|
+
import { homedir } from "os";
|
|
709
|
+
import { dirname, join } from "path";
|
|
710
|
+
function defaultConfigPath() {
|
|
711
|
+
return join(homedir(), ".reasonix", "config.json");
|
|
712
|
+
}
|
|
713
|
+
function readConfig(path = defaultConfigPath()) {
|
|
714
|
+
try {
|
|
715
|
+
const raw = readFileSync2(path, "utf8");
|
|
716
|
+
const parsed = JSON.parse(raw);
|
|
717
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
718
|
+
} catch {
|
|
719
|
+
}
|
|
720
|
+
return {};
|
|
721
|
+
}
|
|
722
|
+
function writeConfig(cfg, path = defaultConfigPath()) {
|
|
723
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
724
|
+
writeFileSync(path, JSON.stringify(cfg, null, 2), "utf8");
|
|
725
|
+
try {
|
|
726
|
+
chmodSync(path, 384);
|
|
727
|
+
} catch {
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
function loadApiKey(path = defaultConfigPath()) {
|
|
731
|
+
if (process.env.DEEPSEEK_API_KEY) return process.env.DEEPSEEK_API_KEY;
|
|
732
|
+
return readConfig(path).apiKey;
|
|
733
|
+
}
|
|
734
|
+
function saveApiKey(key, path = defaultConfigPath()) {
|
|
735
|
+
const cfg = readConfig(path);
|
|
736
|
+
cfg.apiKey = key.trim();
|
|
737
|
+
writeConfig(cfg, path);
|
|
738
|
+
}
|
|
739
|
+
function isPlausibleKey(key) {
|
|
740
|
+
const trimmed = key.trim();
|
|
741
|
+
return /^sk-[A-Za-z0-9_-]{16,}$/.test(trimmed);
|
|
742
|
+
}
|
|
743
|
+
function redactKey(key) {
|
|
744
|
+
if (!key) return "";
|
|
745
|
+
if (key.length <= 12) return "****";
|
|
746
|
+
return `${key.slice(0, 6)}\u2026${key.slice(-4)}`;
|
|
747
|
+
}
|
|
748
|
+
|
|
620
749
|
// src/index.ts
|
|
621
750
|
var VERSION = "0.0.1";
|
|
622
751
|
|
|
623
752
|
// src/cli/commands/chat.tsx
|
|
624
753
|
import { render } from "ink";
|
|
625
|
-
import
|
|
754
|
+
import React7, { useState as useState3 } from "react";
|
|
626
755
|
|
|
627
756
|
// src/cli/ui/App.tsx
|
|
628
757
|
import { createWriteStream } from "fs";
|
|
@@ -785,7 +914,7 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
785
914
|
}
|
|
786
915
|
if (event.role === "assistant") {
|
|
787
916
|
if (event.streaming) return /* @__PURE__ */ React2.createElement(StreamingAssistant, { event });
|
|
788
|
-
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);
|
|
917
|
+
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);
|
|
789
918
|
}
|
|
790
919
|
if (event.role === "tool") {
|
|
791
920
|
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)));
|
|
@@ -798,6 +927,14 @@ var EventRow = React2.memo(function EventRow2({ event }) {
|
|
|
798
927
|
}
|
|
799
928
|
return /* @__PURE__ */ React2.createElement(Box2, null, /* @__PURE__ */ React2.createElement(Text2, null, event.text));
|
|
800
929
|
});
|
|
930
|
+
function PlanStateBlock({ planState }) {
|
|
931
|
+
const lines = [];
|
|
932
|
+
if (planState.subgoals.length) lines.push(["subgoals", planState.subgoals]);
|
|
933
|
+
if (planState.hypotheses.length) lines.push(["hypotheses", planState.hypotheses]);
|
|
934
|
+
if (planState.uncertainties.length) lines.push(["uncertainties", planState.uncertainties]);
|
|
935
|
+
if (planState.rejectedPaths.length) lines.push(["rejected", planState.rejectedPaths]);
|
|
936
|
+
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 ")}`)));
|
|
937
|
+
}
|
|
801
938
|
function ReasoningBlock({ reasoning }) {
|
|
802
939
|
const max = 220;
|
|
803
940
|
const flat = reasoning.replace(/\s+/g, " ").trim();
|
|
@@ -833,13 +970,15 @@ function PromptInput({
|
|
|
833
970
|
disabled,
|
|
834
971
|
placeholder
|
|
835
972
|
}) {
|
|
836
|
-
|
|
973
|
+
const effectivePlaceholder = disabled ? placeholder ?? "\u2026waiting for response\u2026" : placeholder ?? 'type a message, or "/exit"';
|
|
974
|
+
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(
|
|
837
975
|
TextInput,
|
|
838
976
|
{
|
|
839
977
|
value,
|
|
840
978
|
onChange,
|
|
841
979
|
onSubmit,
|
|
842
|
-
|
|
980
|
+
focus: !disabled,
|
|
981
|
+
placeholder: effectivePlaceholder
|
|
843
982
|
}
|
|
844
983
|
));
|
|
845
984
|
}
|
|
@@ -855,7 +994,7 @@ function StatsPanel({ summary, model, prefixHash }) {
|
|
|
855
994
|
|
|
856
995
|
// src/cli/ui/App.tsx
|
|
857
996
|
var FLUSH_INTERVAL_MS = 60;
|
|
858
|
-
function App({ model, system, transcript }) {
|
|
997
|
+
function App({ model, system, transcript, harvest: harvest2 }) {
|
|
859
998
|
const { exit } = useApp();
|
|
860
999
|
const [historical, setHistorical] = useState([]);
|
|
861
1000
|
const [streaming, setStreaming] = useState(null);
|
|
@@ -882,10 +1021,10 @@ function App({ model, system, transcript }) {
|
|
|
882
1021
|
if (loopRef.current) return loopRef.current;
|
|
883
1022
|
const client = new DeepSeekClient();
|
|
884
1023
|
const prefix = new ImmutablePrefix({ system });
|
|
885
|
-
const l = new CacheFirstLoop({ client, prefix, model });
|
|
1024
|
+
const l = new CacheFirstLoop({ client, prefix, model, harvest: harvest2 });
|
|
886
1025
|
loopRef.current = l;
|
|
887
1026
|
return l;
|
|
888
|
-
}, [model, system]);
|
|
1027
|
+
}, [model, system, harvest2]);
|
|
889
1028
|
const prefixHash = loop.prefix.fingerprint;
|
|
890
1029
|
const writeTranscript = useCallback((ev) => {
|
|
891
1030
|
transcriptRef.current?.write(
|
|
@@ -952,6 +1091,7 @@ function App({ model, system, transcript }) {
|
|
|
952
1091
|
role: "assistant",
|
|
953
1092
|
text: ev.content || streamRef.text,
|
|
954
1093
|
reasoning: streamRef.reasoning || void 0,
|
|
1094
|
+
planState: ev.planState,
|
|
955
1095
|
stats: ev.stats,
|
|
956
1096
|
repair: repairNote || void 0,
|
|
957
1097
|
streaming: false
|
|
@@ -995,23 +1135,117 @@ function describeRepair(repair) {
|
|
|
995
1135
|
return parts.length ? `[repair] ${parts.join(", ")}` : "";
|
|
996
1136
|
}
|
|
997
1137
|
|
|
1138
|
+
// src/cli/ui/Setup.tsx
|
|
1139
|
+
import { Box as Box6, Text as Text5, useApp as useApp2 } from "ink";
|
|
1140
|
+
import TextInput2 from "ink-text-input";
|
|
1141
|
+
import React6, { useState as useState2 } from "react";
|
|
1142
|
+
function Setup({ onReady }) {
|
|
1143
|
+
const [value, setValue] = useState2("");
|
|
1144
|
+
const [error, setError] = useState2(null);
|
|
1145
|
+
const { exit } = useApp2();
|
|
1146
|
+
const handleSubmit = (raw) => {
|
|
1147
|
+
const trimmed = raw.trim();
|
|
1148
|
+
if (trimmed === "/exit" || trimmed === "/quit") {
|
|
1149
|
+
exit();
|
|
1150
|
+
return;
|
|
1151
|
+
}
|
|
1152
|
+
if (!isPlausibleKey(trimmed)) {
|
|
1153
|
+
setError("Doesn't look like a DeepSeek key. They start with 'sk-' and are 30+ chars.");
|
|
1154
|
+
setValue("");
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
try {
|
|
1158
|
+
saveApiKey(trimmed);
|
|
1159
|
+
} catch (err) {
|
|
1160
|
+
setError(`Could not save key: ${err.message}`);
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
onReady(trimmed);
|
|
1164
|
+
};
|
|
1165
|
+
return /* @__PURE__ */ React6.createElement(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "cyan", paddingX: 1 }, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "Welcome to Reasonix."), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, null, "Paste your DeepSeek API key to get started.")), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "Get one (free credit on signup): https://platform.deepseek.com/api_keys"), /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "Saved locally to ", defaultConfigPath()), /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { bold: true, color: "cyan" }, "key \u203A "), /* @__PURE__ */ React6.createElement(
|
|
1166
|
+
TextInput2,
|
|
1167
|
+
{
|
|
1168
|
+
value,
|
|
1169
|
+
onChange: setValue,
|
|
1170
|
+
onSubmit: handleSubmit,
|
|
1171
|
+
mask: "\u2022",
|
|
1172
|
+
placeholder: "sk-..."
|
|
1173
|
+
}
|
|
1174
|
+
)), error ? /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { color: "red" }, error)) : value ? /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "preview: ", redactKey(value))) : null, /* @__PURE__ */ React6.createElement(Box6, { marginTop: 1 }, /* @__PURE__ */ React6.createElement(Text5, { dimColor: true }, "(Type /exit to abort.)")));
|
|
1175
|
+
}
|
|
1176
|
+
|
|
998
1177
|
// src/cli/commands/chat.tsx
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
if (!
|
|
1002
|
-
|
|
1003
|
-
|
|
1178
|
+
function Root({ initialKey, ...appProps }) {
|
|
1179
|
+
const [key, setKey] = useState3(initialKey);
|
|
1180
|
+
if (!key) {
|
|
1181
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1182
|
+
Setup,
|
|
1183
|
+
{
|
|
1184
|
+
onReady: (k) => {
|
|
1185
|
+
process.env.DEEPSEEK_API_KEY = k;
|
|
1186
|
+
setKey(k);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
);
|
|
1004
1190
|
}
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1191
|
+
process.env.DEEPSEEK_API_KEY = key;
|
|
1192
|
+
return /* @__PURE__ */ React7.createElement(
|
|
1193
|
+
App,
|
|
1194
|
+
{
|
|
1195
|
+
model: appProps.model,
|
|
1196
|
+
system: appProps.system,
|
|
1197
|
+
transcript: appProps.transcript,
|
|
1198
|
+
harvest: appProps.harvest
|
|
1199
|
+
}
|
|
1008
1200
|
);
|
|
1201
|
+
}
|
|
1202
|
+
async function chatCommand(opts) {
|
|
1203
|
+
loadDotenv();
|
|
1204
|
+
const initialKey = loadApiKey();
|
|
1205
|
+
const { waitUntilExit } = render(/* @__PURE__ */ React7.createElement(Root, { initialKey, ...opts }), {
|
|
1206
|
+
exitOnCtrlC: true
|
|
1207
|
+
});
|
|
1009
1208
|
await waitUntilExit();
|
|
1010
1209
|
}
|
|
1011
1210
|
|
|
1012
1211
|
// src/cli/commands/run.ts
|
|
1212
|
+
import { stdin, stdout } from "process";
|
|
1213
|
+
import { createInterface } from "readline/promises";
|
|
1214
|
+
async function ensureApiKey() {
|
|
1215
|
+
const existing = loadApiKey();
|
|
1216
|
+
if (existing) return existing;
|
|
1217
|
+
if (!stdin.isTTY) {
|
|
1218
|
+
process.stderr.write(
|
|
1219
|
+
"DEEPSEEK_API_KEY is not set and stdin is not a TTY (cannot prompt).\nSet the env var, or run `reasonix chat` once interactively to save a key.\n"
|
|
1220
|
+
);
|
|
1221
|
+
process.exit(1);
|
|
1222
|
+
}
|
|
1223
|
+
process.stdout.write(
|
|
1224
|
+
"DeepSeek API key not configured.\nGet one at https://platform.deepseek.com/api_keys\n"
|
|
1225
|
+
);
|
|
1226
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
1227
|
+
try {
|
|
1228
|
+
while (true) {
|
|
1229
|
+
const answer = (await rl.question("API key \u203A ")).trim();
|
|
1230
|
+
if (!answer) continue;
|
|
1231
|
+
if (!isPlausibleKey(answer)) {
|
|
1232
|
+
process.stdout.write("Invalid format. Keys start with 'sk-' and are 30+ chars.\n");
|
|
1233
|
+
continue;
|
|
1234
|
+
}
|
|
1235
|
+
saveApiKey(answer);
|
|
1236
|
+
process.stdout.write(`Saved to ${defaultConfigPath()}
|
|
1237
|
+
|
|
1238
|
+
`);
|
|
1239
|
+
return answer;
|
|
1240
|
+
}
|
|
1241
|
+
} finally {
|
|
1242
|
+
rl.close();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1013
1245
|
async function runCommand(opts) {
|
|
1014
1246
|
loadDotenv();
|
|
1247
|
+
const apiKey = await ensureApiKey();
|
|
1248
|
+
process.env.DEEPSEEK_API_KEY = apiKey;
|
|
1015
1249
|
const client = new DeepSeekClient();
|
|
1016
1250
|
const prefix = new ImmutablePrefix({ system: opts.system });
|
|
1017
1251
|
const loop = new CacheFirstLoop({ client, prefix, model: opts.model });
|
|
@@ -1034,13 +1268,13 @@ async function runCommand(opts) {
|
|
|
1034
1268
|
}
|
|
1035
1269
|
|
|
1036
1270
|
// src/cli/commands/stats.ts
|
|
1037
|
-
import { existsSync, readFileSync as
|
|
1271
|
+
import { existsSync, readFileSync as readFileSync3 } from "fs";
|
|
1038
1272
|
function statsCommand(opts) {
|
|
1039
1273
|
if (!existsSync(opts.transcript)) {
|
|
1040
1274
|
console.error(`no such transcript: ${opts.transcript}`);
|
|
1041
1275
|
process.exit(1);
|
|
1042
1276
|
}
|
|
1043
|
-
const lines =
|
|
1277
|
+
const lines = readFileSync3(opts.transcript, "utf8").split(/\r?\n/).filter(Boolean);
|
|
1044
1278
|
let assistantTurns = 0;
|
|
1045
1279
|
let toolCalls = 0;
|
|
1046
1280
|
let lastTurn = 0;
|
|
@@ -1068,11 +1302,15 @@ function versionCommand() {
|
|
|
1068
1302
|
var DEFAULT_SYSTEM = "You are Reasonix, a helpful DeepSeek-powered assistant. Be concise and accurate. Use tools when available.";
|
|
1069
1303
|
var program = new Command();
|
|
1070
1304
|
program.name("reasonix").description("DeepSeek-native agent framework \u2014 built for cache hits and cheap tokens.").version(VERSION);
|
|
1071
|
-
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").
|
|
1305
|
+
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(
|
|
1306
|
+
"--harvest",
|
|
1307
|
+
"Extract typed plan state from R1 reasoning (Pillar 2, adds a cheap V3 call per turn)"
|
|
1308
|
+
).action(async (opts) => {
|
|
1072
1309
|
await chatCommand({
|
|
1073
1310
|
model: opts.model,
|
|
1074
1311
|
system: opts.system,
|
|
1075
|
-
transcript: opts.transcript
|
|
1312
|
+
transcript: opts.transcript,
|
|
1313
|
+
harvest: !!opts.harvest
|
|
1076
1314
|
});
|
|
1077
1315
|
});
|
|
1078
1316
|
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) => {
|