@atlasharness/atlascode 0.1.57 → 0.1.58

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.
Files changed (2) hide show
  1. package/dist/cli.js +183 -78
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -26920,6 +26920,63 @@ function gatewayUnreachableRemediationHint(error) {
26920
26920
  return null;
26921
26921
  return "IFF 不可达:已切人工确认 —— /doctor 排查";
26922
26922
  }
26923
+ function modelRetryBackoffMs(attempt, baseMs = MODEL_RETRY_BACKOFF_BASE_MS, capMs = MODEL_RETRY_BACKOFF_CAP_MS) {
26924
+ const base = Math.max(0, baseMs);
26925
+ const cap = Math.max(base, capMs);
26926
+ if (attempt <= 0)
26927
+ return Math.min(base, cap);
26928
+ return Math.min(base * 2 ** attempt, cap);
26929
+ }
26930
+ function sleepSignalAware(ms, signal) {
26931
+ if (ms <= 0)
26932
+ return Promise.resolve();
26933
+ return new Promise((resolve) => {
26934
+ if (signal?.aborted) {
26935
+ resolve();
26936
+ return;
26937
+ }
26938
+ let settled = false;
26939
+ const timer = setTimeout(() => {
26940
+ settled = true;
26941
+ if (signal)
26942
+ signal.removeEventListener("abort", onAbort);
26943
+ resolve();
26944
+ }, ms);
26945
+ function onAbort() {
26946
+ if (settled)
26947
+ return;
26948
+ settled = true;
26949
+ clearTimeout(timer);
26950
+ resolve();
26951
+ }
26952
+ if (signal)
26953
+ signal.addEventListener("abort", onAbort, { once: true });
26954
+ });
26955
+ }
26956
+ function buildStreamErrorEvent(err, signal) {
26957
+ const message = err instanceof Error ? err.message || err.name : String(err);
26958
+ const code = isClientRequestTimeout(err) ? "TIMEOUT" : "API_ERROR";
26959
+ return { type: "error", code, message, retryable: shouldRetryModelError(err, signal) };
26960
+ }
26961
+ function buildStreamContentBlocks(reasoningText, fullText, toolCalls) {
26962
+ const blocks = [];
26963
+ if (reasoningText)
26964
+ blocks.push({ type: "thinking", thinking: reasoningText, signature: "first-party" });
26965
+ if (fullText)
26966
+ blocks.push({ type: "text", text: fullText });
26967
+ blocks.push(...[...toolCalls.values()].filter((t) => t.name).map((t) => {
26968
+ let input = {};
26969
+ if (t.args) {
26970
+ try {
26971
+ input = JSON.parse(t.args);
26972
+ } catch {
26973
+ input = { _raw_arguments: t.args };
26974
+ }
26975
+ }
26976
+ return { type: "tool_use", id: t.id || `toolu_${randomUUID()}`, name: t.name, input };
26977
+ }));
26978
+ return blocks;
26979
+ }
26923
26980
  function startLlmWaitHeartbeat(modelId, attempt, totalRetries) {
26924
26981
  const startedAt = Date.now();
26925
26982
  const id = setInterval(() => {
@@ -26934,10 +26991,16 @@ class OpenAIProvider {
26934
26991
  maxRetriesPerModel;
26935
26992
  timeoutMs;
26936
26993
  timeoutResolver;
26937
- constructor(maxRetriesPerModel = 3, timeoutMs = LLM_TIMEOUT_DEFAULT_MS, timeoutResolver) {
26994
+ retryBackoffBaseMs;
26995
+ retryBackoffCapMs;
26996
+ retryBackoffSleep;
26997
+ constructor(maxRetriesPerModel = 3, timeoutMs = LLM_TIMEOUT_DEFAULT_MS, timeoutResolver, retryBackoff) {
26938
26998
  this.maxRetriesPerModel = maxRetriesPerModel;
26939
26999
  this.timeoutMs = timeoutMs;
26940
27000
  this.timeoutResolver = timeoutResolver;
27001
+ this.retryBackoffBaseMs = retryBackoff?.baseMs ?? MODEL_RETRY_BACKOFF_BASE_MS;
27002
+ this.retryBackoffCapMs = retryBackoff?.capMs ?? MODEL_RETRY_BACKOFF_CAP_MS;
27003
+ this.retryBackoffSleep = retryBackoff?.sleep ?? ((ms, signal) => sleepSignalAware(ms, signal));
26941
27004
  }
26942
27005
  getTimeoutMs() {
26943
27006
  return this.timeoutResolver ? this.timeoutResolver() : this.timeoutMs;
@@ -27030,6 +27093,9 @@ class OpenAIProvider {
27030
27093
  lastErr = err;
27031
27094
  if (!shouldRetryModelError(err, args.signal))
27032
27095
  break;
27096
+ if (attempt < this.maxRetriesPerModel - 1) {
27097
+ await this.retryBackoffSleep(modelRetryBackoffMs(attempt, this.retryBackoffBaseMs, this.retryBackoffCapMs), args.signal);
27098
+ }
27033
27099
  }
27034
27100
  }
27035
27101
  }
@@ -27045,6 +27111,7 @@ class OpenAIProvider {
27045
27111
  const attemptRefs = refs;
27046
27112
  let lastErr = null;
27047
27113
  let succeeded = false;
27114
+ let partialRetained = false;
27048
27115
  for (const ref of attemptRefs) {
27049
27116
  const entry = resolveModel(ref);
27050
27117
  if (!entry) {
@@ -27073,22 +27140,27 @@ class OpenAIProvider {
27073
27140
  if (args.signal?.aborted)
27074
27141
  return;
27075
27142
  try {
27076
- yield* this.streamOneAttempt(client, params, entry, args.signal, attempt, this.maxRetriesPerModel);
27143
+ const outcome = yield* this.streamOneAttempt(client, params, entry, args.signal, attempt, this.maxRetriesPerModel);
27144
+ if (outcome.partialRetained) {
27145
+ partialRetained = true;
27146
+ break;
27147
+ }
27077
27148
  succeeded = true;
27078
27149
  break;
27079
27150
  } catch (err) {
27080
27151
  lastErr = err;
27081
27152
  if (!shouldRetryModelError(err, args.signal))
27082
27153
  break;
27154
+ if (attempt < this.maxRetriesPerModel - 1) {
27155
+ await this.retryBackoffSleep(modelRetryBackoffMs(attempt, this.retryBackoffBaseMs, this.retryBackoffCapMs), args.signal);
27156
+ }
27083
27157
  }
27084
27158
  }
27085
- if (succeeded || args.signal?.aborted)
27159
+ if (succeeded || partialRetained || args.signal?.aborted)
27086
27160
  return;
27087
27161
  }
27088
27162
  if (lastErr) {
27089
- const message = lastErr instanceof Error ? lastErr.message || lastErr.name : String(lastErr);
27090
- const code = isClientRequestTimeout(lastErr) ? "TIMEOUT" : "API_ERROR";
27091
- yield { type: "error", code, message, retryable: shouldRetryModelError(lastErr, args.signal) };
27163
+ yield buildStreamErrorEvent(lastErr, args.signal);
27092
27164
  return;
27093
27165
  }
27094
27166
  throw new Error(`No models configured for role '${args.role}'.`);
@@ -27107,66 +27179,111 @@ class OpenAIProvider {
27107
27179
  const toolCalls = new Map;
27108
27180
  let finishReason = null;
27109
27181
  let usage = null;
27110
- for await (const chunk of stream) {
27111
- const choice = chunk.choices?.[0];
27112
- const delta = choice?.delta;
27113
- if (delta?.content) {
27114
- fullText += delta.content;
27115
- yield { type: "text_delta", text: delta.content };
27116
- }
27117
- if (delta?.reasoning_content)
27118
- reasoningText += delta.reasoning_content;
27119
- if (delta?.tool_calls) {
27120
- for (const tc of delta.tool_calls) {
27121
- const idx = tc.index ?? 0;
27122
- const cur = toolCalls.get(idx) ?? { id: "", name: "", args: "" };
27123
- if (tc.id)
27124
- cur.id = tc.id;
27125
- if (tc.function?.name) {
27126
- cur.name = tc.function.name;
27127
- if (currentToolCall) {
27128
- let input = {};
27129
- if (currentToolCall.args) {
27130
- try {
27131
- input = JSON.parse(currentToolCall.args);
27132
- } catch {
27133
- input = { _raw_arguments: currentToolCall.args };
27182
+ let streamError = null;
27183
+ try {
27184
+ for await (const chunk of stream) {
27185
+ const choice = chunk.choices?.[0];
27186
+ const delta = choice?.delta;
27187
+ if (delta?.content) {
27188
+ fullText += delta.content;
27189
+ yield { type: "text_delta", text: delta.content };
27190
+ }
27191
+ if (delta?.reasoning_content)
27192
+ reasoningText += delta.reasoning_content;
27193
+ if (delta?.tool_calls) {
27194
+ for (const tc of delta.tool_calls) {
27195
+ const idx = tc.index ?? 0;
27196
+ const cur = toolCalls.get(idx) ?? { id: "", name: "", args: "" };
27197
+ if (tc.id)
27198
+ cur.id = tc.id;
27199
+ if (tc.function?.name) {
27200
+ cur.name = tc.function.name;
27201
+ if (currentToolCall) {
27202
+ let input = {};
27203
+ if (currentToolCall.args) {
27204
+ try {
27205
+ input = JSON.parse(currentToolCall.args);
27206
+ } catch {
27207
+ input = { _raw_arguments: currentToolCall.args };
27208
+ }
27134
27209
  }
27210
+ yield { type: "tool_use_end", id: currentToolCall.id, name: currentToolCall.name, input };
27135
27211
  }
27136
- yield { type: "tool_use_end", id: currentToolCall.id, name: currentToolCall.name, input };
27212
+ currentToolCall = { id: cur.id, name: cur.name, args: cur.args };
27213
+ yield { type: "tool_use_start", id: cur.id, name: cur.name };
27137
27214
  }
27138
- currentToolCall = { id: cur.id, name: cur.name, args: cur.args };
27139
- yield { type: "tool_use_start", id: cur.id, name: cur.name };
27140
- }
27141
- if (tc.function?.arguments) {
27142
- cur.args += tc.function.arguments;
27143
- if (currentToolCall && cur.name) {
27144
- currentToolCall.args = cur.args;
27145
- yield { type: "tool_use_delta", id: cur.id, inputJsonDelta: tc.function.arguments };
27215
+ if (tc.function?.arguments) {
27216
+ cur.args += tc.function.arguments;
27217
+ if (currentToolCall && cur.name) {
27218
+ currentToolCall.args = cur.args;
27219
+ yield { type: "tool_use_delta", id: cur.id, inputJsonDelta: tc.function.arguments };
27220
+ }
27146
27221
  }
27222
+ toolCalls.set(idx, cur);
27147
27223
  }
27148
- toolCalls.set(idx, cur);
27149
27224
  }
27150
- }
27151
- if (choice?.finish_reason) {
27152
- if (currentToolCall) {
27153
- let input = {};
27154
- if (currentToolCall.args) {
27155
- try {
27156
- input = JSON.parse(currentToolCall.args);
27157
- } catch {
27158
- input = { _raw_arguments: currentToolCall.args };
27225
+ if (choice?.finish_reason) {
27226
+ if (currentToolCall) {
27227
+ let input = {};
27228
+ if (currentToolCall.args) {
27229
+ try {
27230
+ input = JSON.parse(currentToolCall.args);
27231
+ } catch {
27232
+ input = { _raw_arguments: currentToolCall.args };
27233
+ }
27159
27234
  }
27235
+ yield { type: "tool_use_end", id: currentToolCall.id, name: currentToolCall.name, input };
27236
+ currentToolCall = null;
27160
27237
  }
27161
- yield { type: "tool_use_end", id: currentToolCall.id, name: currentToolCall.name, input };
27162
- currentToolCall = null;
27238
+ finishReason = choice.finish_reason;
27163
27239
  }
27164
- finishReason = choice.finish_reason;
27240
+ if (chunk.usage)
27241
+ usage = mapOpenAIUsage(chunk.usage);
27242
+ if (signal?.aborted)
27243
+ break;
27165
27244
  }
27166
- if (chunk.usage)
27167
- usage = mapOpenAIUsage(chunk.usage);
27168
- if (signal?.aborted)
27169
- break;
27245
+ } catch (err) {
27246
+ streamError = err;
27247
+ }
27248
+ if (streamError !== null) {
27249
+ const partialBlocks = buildStreamContentBlocks(reasoningText, fullText, toolCalls);
27250
+ if (partialBlocks.length === 0)
27251
+ throw streamError;
27252
+ yield {
27253
+ type: "message_stop",
27254
+ stopReason: "interrupted",
27255
+ usage: usage ? {
27256
+ input_tokens: usage.input_tokens ?? 0,
27257
+ output_tokens: usage.output_tokens ?? 0,
27258
+ cache_creation_input_tokens: usage.cache_creation_input_tokens ?? 0,
27259
+ cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
27260
+ } : undefined
27261
+ };
27262
+ const emptyUsage = mapOpenAIUsage(null);
27263
+ const partialMsg = {
27264
+ uuid: randomUUID(),
27265
+ timestamp: new Date().toISOString(),
27266
+ message: {
27267
+ id: randomUUID(),
27268
+ container: null,
27269
+ model: entry.modelId,
27270
+ role: "assistant",
27271
+ stop_reason: "interrupted",
27272
+ stop_sequence: "",
27273
+ type: "message",
27274
+ usage: usage || emptyUsage,
27275
+ content: partialBlocks
27276
+ }
27277
+ };
27278
+ yield {
27279
+ type: "assistant",
27280
+ uuid: partialMsg.uuid,
27281
+ timestamp: partialMsg.timestamp,
27282
+ message: partialMsg.message,
27283
+ interrupted: true
27284
+ };
27285
+ yield buildStreamErrorEvent(streamError, signal);
27286
+ return { partialRetained: true };
27170
27287
  }
27171
27288
  yield {
27172
27289
  type: "message_stop",
@@ -27178,27 +27295,11 @@ class OpenAIProvider {
27178
27295
  cache_read_input_tokens: usage.cache_read_input_tokens ?? 0
27179
27296
  } : undefined
27180
27297
  };
27181
- const contentBlocks = [];
27182
- if (reasoningText)
27183
- contentBlocks.push({ type: "thinking", thinking: reasoningText, signature: "first-party" });
27184
- if (fullText)
27185
- contentBlocks.push({ type: "text", text: fullText });
27186
- const toolUseBlocks = [...toolCalls.values()].filter((t) => t.name).map((t) => {
27187
- let input = {};
27188
- if (t.args) {
27189
- try {
27190
- input = JSON.parse(t.args);
27191
- } catch {
27192
- input = { _raw_arguments: t.args };
27193
- }
27194
- }
27195
- return { type: "tool_use", id: t.id || `toolu_${randomUUID()}`, name: t.name, input };
27196
- });
27197
- contentBlocks.push(...toolUseBlocks);
27298
+ const contentBlocks = buildStreamContentBlocks(reasoningText, fullText, toolCalls);
27198
27299
  if (contentBlocks.length === 0) {
27199
27300
  contentBlocks.push({ type: "text", text: PROVIDER_EMPTY_CONTENT_PLACEHOLDER });
27200
27301
  }
27201
- const stopReason = toolUseBlocks.length > 0 ? "tool_use" : finishReason === "length" ? "max_tokens" : "end_turn";
27302
+ const stopReason = contentBlocks.some((b) => b.type === "tool_use") ? "tool_use" : finishReason === "length" ? "max_tokens" : "end_turn";
27202
27303
  const emptyUsage = mapOpenAIUsage(null);
27203
27304
  const finalMsg = {
27204
27305
  uuid: randomUUID(),
@@ -27216,6 +27317,7 @@ class OpenAIProvider {
27216
27317
  }
27217
27318
  };
27218
27319
  yield { type: "assistant", uuid: finalMsg.uuid, timestamp: finalMsg.timestamp, message: finalMsg.message };
27320
+ return { partialRetained: false };
27219
27321
  }
27220
27322
  async healthCheck(role) {
27221
27323
  const refs = getRoleModels(role);
@@ -27332,7 +27434,7 @@ class OpenAIProvider {
27332
27434
  }
27333
27435
  }
27334
27436
  }
27335
- var PROVIDER_EMPTY_CONTENT_PLACEHOLDER = "(provider: empty response)";
27437
+ var PROVIDER_EMPTY_CONTENT_PLACEHOLDER = "(provider: empty response)", MODEL_RETRY_BACKOFF_BASE_MS = 500, MODEL_RETRY_BACKOFF_CAP_MS = 8000;
27336
27438
  var init_modelprovider = __esm(() => {
27337
27439
  init_openai();
27338
27440
  init_clients();
@@ -27786,7 +27888,7 @@ function turnRecoveryBackoffMs(attempt, baseMs, capMs) {
27786
27888
  return Math.min(base, cap);
27787
27889
  return Math.min(base * 2 ** attempt, cap);
27788
27890
  }
27789
- function sleepSignalAware(ms, signal) {
27891
+ function sleepSignalAware2(ms, signal) {
27790
27892
  if (ms <= 0)
27791
27893
  return Promise.resolve();
27792
27894
  return new Promise((resolve) => {
@@ -27829,7 +27931,7 @@ async function withTurnRecovery(attempt, options = {}) {
27829
27931
  if (i < cfg.maxRetries && shouldRetryModelError(err, options.signal)) {
27830
27932
  const backoff = turnRecoveryBackoffMs(i, cfg.backoffBaseMs, cfg.backoffCapMs);
27831
27933
  if (backoff > 0) {
27832
- await sleepSignalAware(backoff, options.signal);
27934
+ await sleepSignalAware2(backoff, options.signal);
27833
27935
  }
27834
27936
  continue;
27835
27937
  }
@@ -430373,7 +430475,10 @@ async function streamCompactSummary({
430373
430475
  context.setResponseLength?.((length) => length + deltaEvent.text.length);
430374
430476
  }
430375
430477
  if (event.type === "assistant") {
430376
- response = event;
430478
+ const assistantEvent = event;
430479
+ if (!assistantEvent.interrupted) {
430480
+ response = assistantEvent;
430481
+ }
430377
430482
  }
430378
430483
  }
430379
430484
  if (response) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlasharness/atlascode",
3
- "version": "0.1.57",
3
+ "version": "0.1.58",
4
4
  "description": "AtlasCode \u2014 Coding Agent for the domestic computing ecosystem (Ascend NPU + domestic LLM backends)",
5
5
  "bin": {
6
6
  "atlas": "dist/cli.js",