@hyperspaceng/neural-coding-agent 0.63.0 → 0.63.2

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 (100) hide show
  1. package/CHANGELOG.md +71 -0
  2. package/README.md +2 -2
  3. package/dist/core/agent-session.d.ts +9 -6
  4. package/dist/core/agent-session.d.ts.map +1 -1
  5. package/dist/core/agent-session.js +98 -54
  6. package/dist/core/agent-session.js.map +1 -1
  7. package/dist/core/auth-storage.d.ts +3 -1
  8. package/dist/core/auth-storage.d.ts.map +1 -1
  9. package/dist/core/auth-storage.js +5 -2
  10. package/dist/core/auth-storage.js.map +1 -1
  11. package/dist/core/compaction/branch-summarization.d.ts +2 -0
  12. package/dist/core/compaction/branch-summarization.d.ts.map +1 -1
  13. package/dist/core/compaction/branch-summarization.js +2 -2
  14. package/dist/core/compaction/branch-summarization.js.map +1 -1
  15. package/dist/core/compaction/compaction.d.ts +3 -3
  16. package/dist/core/compaction/compaction.d.ts.map +1 -1
  17. package/dist/core/compaction/compaction.js +27 -26
  18. package/dist/core/compaction/compaction.js.map +1 -1
  19. package/dist/core/export-html/index.d.ts.map +1 -1
  20. package/dist/core/export-html/index.js +5 -4
  21. package/dist/core/export-html/index.js.map +1 -1
  22. package/dist/core/extensions/types.d.ts +7 -1
  23. package/dist/core/extensions/types.d.ts.map +1 -1
  24. package/dist/core/extensions/types.js.map +1 -1
  25. package/dist/core/model-registry.d.ts +18 -2
  26. package/dist/core/model-registry.d.ts.map +1 -1
  27. package/dist/core/model-registry.js +83 -69
  28. package/dist/core/model-registry.js.map +1 -1
  29. package/dist/core/model-resolver.d.ts.map +1 -1
  30. package/dist/core/model-resolver.js +4 -4
  31. package/dist/core/model-resolver.js.map +1 -1
  32. package/dist/core/package-manager.d.ts.map +1 -1
  33. package/dist/core/package-manager.js +88 -24
  34. package/dist/core/package-manager.js.map +1 -1
  35. package/dist/core/resolve-config-value.d.ts +6 -0
  36. package/dist/core/resolve-config-value.d.ts.map +1 -1
  37. package/dist/core/resolve-config-value.js +37 -5
  38. package/dist/core/resolve-config-value.js.map +1 -1
  39. package/dist/core/sdk.d.ts +1 -1
  40. package/dist/core/sdk.d.ts.map +1 -1
  41. package/dist/core/sdk.js +13 -22
  42. package/dist/core/sdk.js.map +1 -1
  43. package/dist/core/settings-manager.d.ts +2 -0
  44. package/dist/core/settings-manager.d.ts.map +1 -1
  45. package/dist/core/settings-manager.js +3 -0
  46. package/dist/core/settings-manager.js.map +1 -1
  47. package/dist/core/timings.d.ts +1 -0
  48. package/dist/core/timings.d.ts.map +1 -1
  49. package/dist/core/timings.js +6 -0
  50. package/dist/core/timings.js.map +1 -1
  51. package/dist/core/tools/edit-diff.d.ts +23 -1
  52. package/dist/core/tools/edit-diff.d.ts.map +1 -1
  53. package/dist/core/tools/edit-diff.js +150 -57
  54. package/dist/core/tools/edit-diff.js.map +1 -1
  55. package/dist/core/tools/edit.d.ts +18 -6
  56. package/dist/core/tools/edit.d.ts.map +1 -1
  57. package/dist/core/tools/edit.js +108 -59
  58. package/dist/core/tools/edit.js.map +1 -1
  59. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  60. package/dist/core/tools/file-mutation-queue.js +4 -4
  61. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  62. package/dist/core/tools/index.d.ts +12 -4
  63. package/dist/core/tools/index.d.ts.map +1 -1
  64. package/dist/main.d.ts.map +1 -1
  65. package/dist/main.js +28 -10
  66. package/dist/main.js.map +1 -1
  67. package/dist/modes/interactive/components/bash-execution.d.ts +0 -1
  68. package/dist/modes/interactive/components/bash-execution.d.ts.map +1 -1
  69. package/dist/modes/interactive/components/bash-execution.js +18 -5
  70. package/dist/modes/interactive/components/bash-execution.js.map +1 -1
  71. package/dist/modes/interactive/components/tool-execution.d.ts +0 -1
  72. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  73. package/dist/modes/interactive/components/tool-execution.js +2 -7
  74. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  75. package/dist/modes/interactive/interactive-mode.d.ts +0 -1
  76. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  77. package/dist/modes/interactive/interactive-mode.js +28 -65
  78. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  79. package/dist/modes/print-mode.d.ts +1 -1
  80. package/dist/modes/print-mode.d.ts.map +1 -1
  81. package/dist/modes/print-mode.js +83 -71
  82. package/dist/modes/print-mode.js.map +1 -1
  83. package/docs/development.md +3 -1
  84. package/docs/extensions.md +13 -2
  85. package/docs/models.md +6 -0
  86. package/docs/rpc.md +11 -2
  87. package/docs/settings.md +12 -0
  88. package/docs/skills.md +3 -2
  89. package/examples/extensions/custom-compaction.ts +17 -4
  90. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  91. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  92. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  93. package/examples/extensions/custom-provider-qwen-cli/package.json +1 -1
  94. package/examples/extensions/handoff.ts +5 -2
  95. package/examples/extensions/qna.ts +5 -2
  96. package/examples/extensions/summarize.ts +15 -4
  97. package/examples/extensions/trigger-compact.ts +11 -1
  98. package/examples/extensions/with-deps/package-lock.json +2 -2
  99. package/examples/extensions/with-deps/package.json +1 -1
  100. package/package.json +5 -4
@@ -134,6 +134,23 @@ export class AgentSession {
134
134
  get modelRegistry() {
135
135
  return this._modelRegistry;
136
136
  }
137
+ async _getRequiredRequestAuth(model) {
138
+ const result = await this._modelRegistry.getApiKeyAndHeaders(model);
139
+ if (!result.ok) {
140
+ throw new Error(result.error);
141
+ }
142
+ if (result.apiKey) {
143
+ return { apiKey: result.apiKey, headers: result.headers };
144
+ }
145
+ const isOAuth = this._modelRegistry.isUsingOAuth(model);
146
+ if (isOAuth) {
147
+ throw new Error(`Authentication failed for "${model.provider}". ` +
148
+ `Credentials may have expired or network is unavailable. ` +
149
+ `Run '/login ${model.provider}' to re-authenticate.`);
150
+ }
151
+ throw new Error(`No API key found for ${model.provider}.\n\n` +
152
+ `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}`);
153
+ }
137
154
  /**
138
155
  * Install tool hooks once on the Agent instance.
139
156
  *
@@ -686,9 +703,7 @@ export class AgentSession {
686
703
  `Use /login or set an API key environment variable. See ${join(getDocsPath(), "providers.md")}\n\n` +
687
704
  "Then use /model to select a model.");
688
705
  }
689
- // Validate API key
690
- const apiKey = await this._modelRegistry.getApiKey(this.model);
691
- if (!apiKey) {
706
+ if (!this._modelRegistry.hasConfiguredAuth(this.model)) {
692
707
  const isOAuth = this._modelRegistry.isUsingOAuth(this.model);
693
708
  if (isOAuth) {
694
709
  throw new Error(`Authentication failed for "${this.model.provider}". ` +
@@ -1065,12 +1080,11 @@ export class AgentSession {
1065
1080
  }
1066
1081
  /**
1067
1082
  * Set model directly.
1068
- * Validates API key, saves to session and settings.
1069
- * @throws Error if no API key available for the model
1083
+ * Validates that auth is configured, saves to session and settings.
1084
+ * @throws Error if no auth is configured for the model
1070
1085
  */
1071
1086
  async setModel(model) {
1072
- const apiKey = await this._modelRegistry.getApiKey(model);
1073
- if (!apiKey) {
1087
+ if (!this._modelRegistry.hasConfiguredAuth(model)) {
1074
1088
  throw new Error(`No API key for ${model.provider}/${model.id}`);
1075
1089
  }
1076
1090
  const previousModel = this.model;
@@ -1094,27 +1108,11 @@ export class AgentSession {
1094
1108
  }
1095
1109
  return this._cycleAvailableModel(direction);
1096
1110
  }
1097
- async _getScopedModelsWithApiKey() {
1098
- const apiKeysByProvider = new Map();
1099
- const result = [];
1100
- for (const scoped of this._scopedModels) {
1101
- const provider = scoped.model.provider;
1102
- let apiKey;
1103
- if (apiKeysByProvider.has(provider)) {
1104
- apiKey = apiKeysByProvider.get(provider);
1105
- }
1106
- else {
1107
- apiKey = await this._modelRegistry.getApiKeyForProvider(provider);
1108
- apiKeysByProvider.set(provider, apiKey);
1109
- }
1110
- if (apiKey) {
1111
- result.push(scoped);
1112
- }
1113
- }
1114
- return result;
1111
+ _getScopedModelsWithAuth() {
1112
+ return this._scopedModels.filter((scoped) => this._modelRegistry.hasConfiguredAuth(scoped.model));
1115
1113
  }
1116
1114
  async _cycleScopedModel(direction) {
1117
- const scopedModels = await this._getScopedModelsWithApiKey();
1115
+ const scopedModels = this._getScopedModelsWithAuth();
1118
1116
  if (scopedModels.length <= 1)
1119
1117
  return undefined;
1120
1118
  const currentModel = this.model;
@@ -1148,10 +1146,6 @@ export class AgentSession {
1148
1146
  const len = availableModels.length;
1149
1147
  const nextIndex = direction === "forward" ? (currentIndex + 1) % len : (currentIndex - 1 + len) % len;
1150
1148
  const nextModel = availableModels[nextIndex];
1151
- const apiKey = await this._modelRegistry.getApiKey(nextModel);
1152
- if (!apiKey) {
1153
- throw new Error(`No API key for ${nextModel.provider}/${nextModel.id}`);
1154
- }
1155
1149
  const thinkingLevel = this._getThinkingLevelForModelSwitch();
1156
1150
  this.agent.setModel(nextModel);
1157
1151
  this.sessionManager.appendModelChange(nextModel.provider, nextModel.id);
@@ -1276,14 +1270,12 @@ export class AgentSession {
1276
1270
  this._disconnectFromAgent();
1277
1271
  await this.abort();
1278
1272
  this._compactionAbortController = new AbortController();
1273
+ this._emit({ type: "compaction_start", reason: "manual" });
1279
1274
  try {
1280
1275
  if (!this.model) {
1281
1276
  throw new Error("No model selected");
1282
1277
  }
1283
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1284
- if (!apiKey) {
1285
- throw new Error(`No API key for ${this.model.provider}`);
1286
- }
1278
+ const { apiKey, headers } = await this._getRequiredRequestAuth(this.model);
1287
1279
  const pathEntries = this.sessionManager.getBranch();
1288
1280
  const settings = this.settingsManager.getCompactionSettings();
1289
1281
  const preparation = prepareCompaction(pathEntries, settings);
@@ -1326,7 +1318,7 @@ export class AgentSession {
1326
1318
  }
1327
1319
  else {
1328
1320
  // Generate compaction result
1329
- const result = await compact(preparation, this.model, apiKey, customInstructions, this._compactionAbortController.signal);
1321
+ const result = await compact(preparation, this.model, apiKey, headers, customInstructions, this._compactionAbortController.signal);
1330
1322
  summary = result.summary;
1331
1323
  firstKeptEntryId = result.firstKeptEntryId;
1332
1324
  tokensBefore = result.tokensBefore;
@@ -1348,12 +1340,33 @@ export class AgentSession {
1348
1340
  fromExtension,
1349
1341
  });
1350
1342
  }
1351
- return {
1343
+ const compactionResult = {
1352
1344
  summary,
1353
1345
  firstKeptEntryId,
1354
1346
  tokensBefore,
1355
1347
  details,
1356
1348
  };
1349
+ this._emit({
1350
+ type: "compaction_end",
1351
+ reason: "manual",
1352
+ result: compactionResult,
1353
+ aborted: false,
1354
+ willRetry: false,
1355
+ });
1356
+ return compactionResult;
1357
+ }
1358
+ catch (error) {
1359
+ const message = error instanceof Error ? error.message : String(error);
1360
+ const aborted = message === "Compaction cancelled" || (error instanceof Error && error.name === "AbortError");
1361
+ this._emit({
1362
+ type: "compaction_end",
1363
+ reason: "manual",
1364
+ result: undefined,
1365
+ aborted,
1366
+ willRetry: false,
1367
+ errorMessage: aborted ? undefined : `Compaction failed: ${message}`,
1368
+ });
1369
+ throw error;
1357
1370
  }
1358
1371
  finally {
1359
1372
  this._compactionAbortController = undefined;
@@ -1409,7 +1422,8 @@ export class AgentSession {
1409
1422
  if (sameModel && isContextOverflow(assistantMessage, contextWindow)) {
1410
1423
  if (this._overflowRecoveryAttempted) {
1411
1424
  this._emit({
1412
- type: "auto_compaction_end",
1425
+ type: "compaction_end",
1426
+ reason: "overflow",
1413
1427
  result: undefined,
1414
1428
  aborted: false,
1415
1429
  willRetry: false,
@@ -1459,22 +1473,41 @@ export class AgentSession {
1459
1473
  */
1460
1474
  async _runAutoCompaction(reason, willRetry) {
1461
1475
  const settings = this.settingsManager.getCompactionSettings();
1462
- this._emit({ type: "auto_compaction_start", reason });
1476
+ this._emit({ type: "compaction_start", reason });
1463
1477
  this._autoCompactionAbortController = new AbortController();
1464
1478
  try {
1465
1479
  if (!this.model) {
1466
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1480
+ this._emit({
1481
+ type: "compaction_end",
1482
+ reason,
1483
+ result: undefined,
1484
+ aborted: false,
1485
+ willRetry: false,
1486
+ });
1467
1487
  return;
1468
1488
  }
1469
- const apiKey = await this._modelRegistry.getApiKey(this.model);
1470
- if (!apiKey) {
1471
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1489
+ const authResult = await this._modelRegistry.getApiKeyAndHeaders(this.model);
1490
+ if (!authResult.ok || !authResult.apiKey) {
1491
+ this._emit({
1492
+ type: "compaction_end",
1493
+ reason,
1494
+ result: undefined,
1495
+ aborted: false,
1496
+ willRetry: false,
1497
+ });
1472
1498
  return;
1473
1499
  }
1500
+ const { apiKey, headers } = authResult;
1474
1501
  const pathEntries = this.sessionManager.getBranch();
1475
1502
  const preparation = prepareCompaction(pathEntries, settings);
1476
1503
  if (!preparation) {
1477
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: false, willRetry: false });
1504
+ this._emit({
1505
+ type: "compaction_end",
1506
+ reason,
1507
+ result: undefined,
1508
+ aborted: false,
1509
+ willRetry: false,
1510
+ });
1478
1511
  return;
1479
1512
  }
1480
1513
  let extensionCompaction;
@@ -1488,7 +1521,13 @@ export class AgentSession {
1488
1521
  signal: this._autoCompactionAbortController.signal,
1489
1522
  }));
1490
1523
  if (extensionResult?.cancel) {
1491
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1524
+ this._emit({
1525
+ type: "compaction_end",
1526
+ reason,
1527
+ result: undefined,
1528
+ aborted: true,
1529
+ willRetry: false,
1530
+ });
1492
1531
  return;
1493
1532
  }
1494
1533
  if (extensionResult?.compaction) {
@@ -1509,14 +1548,20 @@ export class AgentSession {
1509
1548
  }
1510
1549
  else {
1511
1550
  // Generate compaction result
1512
- const compactResult = await compact(preparation, this.model, apiKey, undefined, this._autoCompactionAbortController.signal);
1551
+ const compactResult = await compact(preparation, this.model, apiKey, headers, undefined, this._autoCompactionAbortController.signal);
1513
1552
  summary = compactResult.summary;
1514
1553
  firstKeptEntryId = compactResult.firstKeptEntryId;
1515
1554
  tokensBefore = compactResult.tokensBefore;
1516
1555
  details = compactResult.details;
1517
1556
  }
1518
1557
  if (this._autoCompactionAbortController.signal.aborted) {
1519
- this._emit({ type: "auto_compaction_end", result: undefined, aborted: true, willRetry: false });
1558
+ this._emit({
1559
+ type: "compaction_end",
1560
+ reason,
1561
+ result: undefined,
1562
+ aborted: true,
1563
+ willRetry: false,
1564
+ });
1520
1565
  return;
1521
1566
  }
1522
1567
  this.sessionManager.appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromExtension);
@@ -1538,7 +1583,7 @@ export class AgentSession {
1538
1583
  tokensBefore,
1539
1584
  details,
1540
1585
  };
1541
- this._emit({ type: "auto_compaction_end", result, aborted: false, willRetry });
1586
+ this._emit({ type: "compaction_end", reason, result, aborted: false, willRetry });
1542
1587
  if (willRetry) {
1543
1588
  const messages = this.agent.state.messages;
1544
1589
  const lastMsg = messages[messages.length - 1];
@@ -1560,7 +1605,8 @@ export class AgentSession {
1560
1605
  catch (error) {
1561
1606
  const errorMessage = error instanceof Error ? error.message : "compaction failed";
1562
1607
  this._emit({
1563
- type: "auto_compaction_end",
1608
+ type: "compaction_end",
1609
+ reason,
1564
1610
  result: undefined,
1565
1611
  aborted: false,
1566
1612
  willRetry: false,
@@ -1720,8 +1766,7 @@ export class AgentSession {
1720
1766
  refreshTools: () => this._refreshToolRegistry(),
1721
1767
  getCommands,
1722
1768
  setModel: async (model) => {
1723
- const key = await this.modelRegistry.getApiKey(model);
1724
- if (!key)
1769
+ if (!this.modelRegistry.hasConfiguredAuth(model))
1725
1770
  return false;
1726
1771
  await this.setModel(model);
1727
1772
  return true;
@@ -2302,14 +2347,12 @@ export class AgentSession {
2302
2347
  let summaryDetails;
2303
2348
  if (options.summarize && entriesToSummarize.length > 0 && !extensionSummary) {
2304
2349
  const model = this.model;
2305
- const apiKey = await this._modelRegistry.getApiKey(model);
2306
- if (!apiKey) {
2307
- throw new Error(`No API key for ${model.provider}`);
2308
- }
2350
+ const { apiKey, headers } = await this._getRequiredRequestAuth(model);
2309
2351
  const branchSummarySettings = this.settingsManager.getBranchSummarySettings();
2310
2352
  const result = await generateBranchSummary(entriesToSummarize, {
2311
2353
  model,
2312
2354
  apiKey,
2355
+ headers,
2313
2356
  signal: this._branchSummaryAbortController.signal,
2314
2357
  customInstructions,
2315
2358
  replaceInstructions,
@@ -2466,6 +2509,7 @@ export class AgentSession {
2466
2509
  total: totalInput + totalOutput + totalCacheRead + totalCacheWrite,
2467
2510
  },
2468
2511
  cost: totalCost,
2512
+ contextUsage: this.getContextUsage(),
2469
2513
  };
2470
2514
  }
2471
2515
  getContextUsage() {