cdsa-harness 0.22.3 → 0.22.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdsa-harness",
3
- "version": "0.22.3",
3
+ "version": "0.22.4",
4
4
  "description": "AI 에이전트의 내부 동작을 단계별로 드러내는 교육용 터미널 하네스. 실시간 스트리밍 + OpenAI/Claude/OpenRouter + MCP + npm 플러그인·크로스포맷 스킬.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/builtins.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // 스킬/플러그인/버전을 바꾼 뒤 `node scripts/gen-builtins.mjs` 로 재생성하세요.
3
3
  import p0 from "../plugins/hwpx_read.mjs";
4
4
 
5
- export const VERSION = "0.22.3";
5
+ export const VERSION = "0.22.4";
6
6
 
7
7
  export const BUILTIN_PLUGINS = [p0];
8
8
 
package/src/llm.js CHANGED
@@ -46,6 +46,18 @@ export class LLMClient {
46
46
  throw new LLMError(`지원하지 않는 provider 입니다: ${this.provider}`);
47
47
  }
48
48
 
49
+ // _post/_openStream 을 감싸, 샘플링 파라미터 거부(400)를 만나면 빼고 1회 재시도.
50
+ async _sendWithSamplingRetry(send, body) {
51
+ if (this._noSampling) this._stripSampling(body);
52
+ try {
53
+ return await send(body);
54
+ } catch (e) {
55
+ if (!this._shouldRetryWithoutSampling(e, body)) throw e;
56
+ this._stripSampling(body);
57
+ return await send(body);
58
+ }
59
+ }
60
+
49
61
  // 스트리밍용: 응답 객체(본문 스트림)를 그대로 받는다.
50
62
  async _openStream(url, headers, body) {
51
63
  const json = JSON.stringify(body);
@@ -71,6 +83,22 @@ export class LLMClient {
71
83
  return describeNetError(e, this.provider, this.timeout, url);
72
84
  }
73
85
 
86
+ // 최신 Claude 모델(Sonnet 5, Opus 4.7+ 등)은 temperature/top_p/top_k 샘플링 파라미터를
87
+ // 거부한다(400: "`temperature` is deprecated for this model."). 모델 목록을 하드코딩하는
88
+ // 대신 그 400 을 감지하면 파라미터를 빼고 1회 재시도하고, 세션 동안 기억해 다시 안 보낸다.
89
+ _shouldRetryWithoutSampling(e, body) {
90
+ if (this._noSampling) return false;
91
+ if (!("temperature" in body || "top_p" in body || "top_k" in body)) return false;
92
+ return e instanceof LLMError && /API 오류 400/.test(e.message) && /temperature|top_p|top_k/.test(e.message);
93
+ }
94
+
95
+ _stripSampling(body) {
96
+ this._noSampling = true;
97
+ delete body.temperature;
98
+ delete body.top_p;
99
+ delete body.top_k;
100
+ }
101
+
74
102
  async _post(url, headers, body) {
75
103
  const json = JSON.stringify(body);
76
104
  const ctrl = new AbortController();
@@ -108,7 +136,7 @@ export class LLMClient {
108
136
  headers["HTTP-Referer"] = "https://github.com/cdsassj00/miniharness";
109
137
  headers["X-Title"] = "CDSA Harness";
110
138
  }
111
- const { payload, latencyMs, bodyBytes } = await this._post(url, headers, body);
139
+ const { payload, latencyMs, bodyBytes } = await this._sendWithSamplingRetry((b) => this._post(url, headers, b), body);
112
140
  const parsed = parseOpenAiReply(payload);
113
141
  return {
114
142
  ...parsed,
@@ -126,7 +154,7 @@ export class LLMClient {
126
154
  "anthropic-version": "2023-06-01",
127
155
  "Content-Type": "application/json",
128
156
  };
129
- const { payload, latencyMs, bodyBytes } = await this._post(url, headers, body);
157
+ const { payload, latencyMs, bodyBytes } = await this._sendWithSamplingRetry((b) => this._post(url, headers, b), body);
130
158
  const parsed = parseAnthropicReply(payload);
131
159
  return {
132
160
  ...parsed,
@@ -148,7 +176,7 @@ export class LLMClient {
148
176
  headers["HTTP-Referer"] = "https://github.com/cdsassj00/miniharness";
149
177
  headers["X-Title"] = "CDSA Harness";
150
178
  }
151
- const { res, started, timer, bodyBytes } = await this._openStream(url, headers, body);
179
+ const { res, started, timer, bodyBytes } = await this._sendWithSamplingRetry((b) => this._openStream(url, headers, b), body);
152
180
  let content = "";
153
181
  const tcMap = new Map(); // index -> {id,name,args}
154
182
  let usage = null;
@@ -183,7 +211,7 @@ export class LLMClient {
183
211
  const body = toAnthropicBody(messages, tools, this.model, this.temperature, this.maxTokens);
184
212
  body.stream = true;
185
213
  const headers = { "x-api-key": this.apiKey, "anthropic-version": "2023-06-01", "Content-Type": "application/json" };
186
- const { res, started, timer, bodyBytes } = await this._openStream(url, headers, body);
214
+ const { res, started, timer, bodyBytes } = await this._sendWithSamplingRetry((b) => this._openStream(url, headers, b), body);
187
215
  let content = "";
188
216
  const blocks = new Map(); // index -> {type,name,id,json}
189
217
  let usage = { input: null, output: null, total: 0 };