reasonix 0.4.28 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -138,6 +138,21 @@ interface UserBalance {
138
138
  is_available: boolean;
139
139
  balance_infos: BalanceInfo[];
140
140
  }
141
+ /**
142
+ * Response shape for DeepSeek's `/models` endpoint. Mirrors the OpenAI
143
+ * models list shape DeepSeek copied — `id` is the model name to pass to
144
+ * `/chat/completions`, `owned_by` is the provider string (always
145
+ * `"deepseek"` today).
146
+ */
147
+ interface ModelInfo {
148
+ id: string;
149
+ object: "model";
150
+ owned_by: string;
151
+ }
152
+ interface ModelList {
153
+ object: "list";
154
+ data: ModelInfo[];
155
+ }
141
156
  interface DeepSeekClientOptions {
142
157
  apiKey?: string;
143
158
  baseUrl?: string;
@@ -163,6 +178,16 @@ declare class DeepSeekClient {
163
178
  getBalance(opts?: {
164
179
  signal?: AbortSignal;
165
180
  }): Promise<UserBalance | null>;
181
+ /**
182
+ * Fetch the model catalog DeepSeek currently exposes. Today this is
183
+ * `deepseek-chat` (V3) and `deepseek-reasoner` (R1), but querying is
184
+ * the only way to learn about new ones without a Reasonix release.
185
+ * Returns null on any network/auth failure so callers can degrade
186
+ * gracefully — e.g. `/models` falls back to the hardcoded hint.
187
+ */
188
+ listModels(opts?: {
189
+ signal?: AbortSignal;
190
+ }): Promise<ModelList | null>;
166
191
  chat(opts: ChatRequestOptions): Promise<ChatResponse>;
167
192
  stream(opts: ChatRequestOptions): AsyncGenerator<StreamChunk>;
168
193
  }
@@ -708,6 +733,7 @@ declare class ToolRegistry {
708
733
  specs(): ToolSpec[];
709
734
  dispatch(name: string, argumentsRaw: string | Record<string, unknown>, opts?: {
710
735
  signal?: AbortSignal;
736
+ maxResultChars?: number;
711
737
  }): Promise<string>;
712
738
  }
713
739
 
package/dist/index.js CHANGED
@@ -154,6 +154,28 @@ var DeepSeekClient = class {
154
154
  return null;
155
155
  }
156
156
  }
157
+ /**
158
+ * Fetch the model catalog DeepSeek currently exposes. Today this is
159
+ * `deepseek-chat` (V3) and `deepseek-reasoner` (R1), but querying is
160
+ * the only way to learn about new ones without a Reasonix release.
161
+ * Returns null on any network/auth failure so callers can degrade
162
+ * gracefully — e.g. `/models` falls back to the hardcoded hint.
163
+ */
164
+ async listModels(opts = {}) {
165
+ try {
166
+ const resp = await this._fetch(`${this.baseUrl}/models`, {
167
+ method: "GET",
168
+ headers: { Authorization: `Bearer ${this.apiKey}` },
169
+ signal: opts.signal
170
+ });
171
+ if (!resp.ok) return null;
172
+ const data = await resp.json();
173
+ if (!data || !Array.isArray(data.data)) return null;
174
+ return data;
175
+ } catch {
176
+ return null;
177
+ }
178
+ }
157
179
  async chat(opts) {
158
180
  const ctrl = new AbortController();
159
181
  const timer = setTimeout(() => ctrl.abort(), this.timeoutMs);
@@ -744,7 +766,8 @@ var ToolRegistry = class {
744
766
  }
745
767
  try {
746
768
  const result = await tool.fn(args, { signal: opts.signal });
747
- return typeof result === "string" ? result : JSON.stringify(result);
769
+ const str = typeof result === "string" ? result : JSON.stringify(result);
770
+ return opts.maxResultChars ? truncateForModel(str, opts.maxResultChars) : str;
748
771
  } catch (err) {
749
772
  const e = err;
750
773
  if (typeof e.toToolResult === "function") {
@@ -1314,8 +1337,8 @@ function countLines(path) {
1314
1337
 
1315
1338
  // src/telemetry.ts
1316
1339
  var DEEPSEEK_PRICING = {
1317
- "deepseek-chat": { inputCacheHit: 0.07, inputCacheMiss: 0.27, output: 1.1 },
1318
- "deepseek-reasoner": { inputCacheHit: 0.14, inputCacheMiss: 0.55, output: 2.19 }
1340
+ "deepseek-chat": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 },
1341
+ "deepseek-reasoner": { inputCacheHit: 0.028, inputCacheMiss: 0.28, output: 0.42 }
1319
1342
  };
1320
1343
  var CLAUDE_SONNET_PRICING = { input: 3, output: 15 };
1321
1344
  var DEEPSEEK_CONTEXT_TOKENS = {
@@ -1870,6 +1893,24 @@ var CacheFirstLoop = class {
1870
1893
  return;
1871
1894
  }
1872
1895
  const ctxMax = DEEPSEEK_CONTEXT_TOKENS[this.model] ?? DEFAULT_CONTEXT_TOKENS;
1896
+ if (usage) {
1897
+ const ratio = usage.promptTokens / ctxMax;
1898
+ if (ratio > 0.6 && ratio <= 0.8) {
1899
+ const before = usage.promptTokens;
1900
+ const soft = this.compact(16e3);
1901
+ if (soft.healedCount > 0) {
1902
+ const approxSaved = Math.round(soft.charsSaved / 4);
1903
+ const after = Math.max(0, before - approxSaved);
1904
+ yield {
1905
+ turn: this._turn,
1906
+ role: "warning",
1907
+ content: `context ${before.toLocaleString()}/${ctxMax.toLocaleString()} (${Math.round(
1908
+ ratio * 100
1909
+ )}%) \u2014 proactively compacted ${soft.healedCount} tool result(s) to 16k, saved ~${approxSaved.toLocaleString()} tokens (now ~${after.toLocaleString()}). Staying ahead of the 80% guard.`
1910
+ };
1911
+ }
1912
+ }
1913
+ }
1873
1914
  if (usage && usage.promptTokens / ctxMax > 0.8) {
1874
1915
  const before = usage.promptTokens;
1875
1916
  const compactResult = this.compact(4e3);
@@ -1932,7 +1973,10 @@ var CacheFirstLoop = class {
1932
1973
  result = `[hook block] ${blocking?.hook.command ?? "<unknown>"}
1933
1974
  ${reason}`;
1934
1975
  } else {
1935
- result = await this.tools.dispatch(name, args, { signal });
1976
+ result = await this.tools.dispatch(name, args, {
1977
+ signal,
1978
+ maxResultChars: DEFAULT_MAX_RESULT_CHARS
1979
+ });
1936
1980
  const postReport = await runHooks({
1937
1981
  hooks: this.hooks,
1938
1982
  payload: {