@ouro.bot/cli 0.1.0-alpha.12 → 0.1.0-alpha.121

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 (130) hide show
  1. package/AdoptionSpecialist.ouro/psyche/SOUL.md +2 -2
  2. package/AdoptionSpecialist.ouro/psyche/identities/monty.md +2 -2
  3. package/README.md +147 -205
  4. package/assets/ouroboros.png +0 -0
  5. package/changelog.json +737 -0
  6. package/dist/heart/active-work.js +622 -0
  7. package/dist/heart/bridges/manager.js +358 -0
  8. package/dist/heart/bridges/state-machine.js +135 -0
  9. package/dist/heart/bridges/store.js +123 -0
  10. package/dist/heart/commitments.js +105 -0
  11. package/dist/heart/config.js +68 -23
  12. package/dist/heart/core.js +528 -100
  13. package/dist/heart/cross-chat-delivery.js +146 -0
  14. package/dist/heart/daemon/agent-discovery.js +81 -0
  15. package/dist/heart/daemon/auth-flow.js +430 -0
  16. package/dist/heart/daemon/daemon-cli.js +1601 -207
  17. package/dist/heart/daemon/daemon-entry.js +43 -2
  18. package/dist/heart/daemon/daemon-runtime-sync.js +212 -0
  19. package/dist/heart/daemon/daemon.js +226 -1
  20. package/dist/heart/daemon/hatch-animation.js +10 -3
  21. package/dist/heart/daemon/hatch-flow.js +7 -82
  22. package/dist/heart/daemon/hooks/bundle-meta.js +92 -0
  23. package/dist/heart/daemon/launchd.js +159 -0
  24. package/dist/heart/daemon/log-tailer.js +4 -3
  25. package/dist/heart/daemon/message-router.js +17 -8
  26. package/dist/heart/daemon/ouro-bot-entry.js +0 -0
  27. package/dist/heart/daemon/ouro-bot-global-installer.js +128 -0
  28. package/dist/heart/daemon/ouro-entry.js +0 -0
  29. package/dist/heart/daemon/ouro-path-installer.js +59 -15
  30. package/dist/heart/daemon/ouro-uti.js +11 -2
  31. package/dist/heart/daemon/ouro-version-manager.js +171 -0
  32. package/dist/heart/daemon/process-manager.js +13 -0
  33. package/dist/heart/daemon/run-hooks.js +37 -0
  34. package/dist/heart/daemon/runtime-logging.js +58 -15
  35. package/dist/heart/daemon/runtime-metadata.js +219 -0
  36. package/dist/heart/daemon/runtime-mode.js +67 -0
  37. package/dist/heart/daemon/sense-manager.js +43 -2
  38. package/dist/heart/daemon/skill-management-installer.js +94 -0
  39. package/dist/heart/daemon/socket-client.js +202 -0
  40. package/dist/heart/daemon/specialist-orchestrator.js +37 -94
  41. package/dist/heart/daemon/specialist-prompt.js +50 -12
  42. package/dist/heart/daemon/specialist-tools.js +211 -60
  43. package/dist/heart/daemon/staged-restart.js +114 -0
  44. package/dist/heart/daemon/thoughts.js +507 -0
  45. package/dist/heart/daemon/update-checker.js +111 -0
  46. package/dist/heart/daemon/update-hooks.js +138 -0
  47. package/dist/heart/daemon/wrapper-publish-guard.js +86 -0
  48. package/dist/heart/delegation.js +62 -0
  49. package/dist/heart/identity.js +64 -21
  50. package/dist/heart/kicks.js +1 -19
  51. package/dist/heart/model-capabilities.js +48 -0
  52. package/dist/heart/obligations.js +197 -0
  53. package/dist/heart/progress-story.js +42 -0
  54. package/dist/heart/provider-failover.js +88 -0
  55. package/dist/heart/provider-ping.js +151 -0
  56. package/dist/heart/providers/anthropic.js +107 -20
  57. package/dist/heart/providers/azure.js +115 -9
  58. package/dist/heart/providers/github-copilot.js +157 -0
  59. package/dist/heart/providers/minimax.js +33 -3
  60. package/dist/heart/providers/openai-codex.js +49 -14
  61. package/dist/heart/safe-workspace.js +381 -0
  62. package/dist/heart/session-activity.js +169 -0
  63. package/dist/heart/session-recall.js +216 -0
  64. package/dist/heart/streaming.js +108 -24
  65. package/dist/heart/target-resolution.js +123 -0
  66. package/dist/heart/tool-loop.js +194 -0
  67. package/dist/heart/turn-coordinator.js +28 -0
  68. package/dist/mind/associative-recall.js +14 -2
  69. package/dist/mind/bundle-manifest.js +70 -0
  70. package/dist/mind/context.js +60 -14
  71. package/dist/mind/first-impressions.js +16 -2
  72. package/dist/mind/friends/channel.js +35 -0
  73. package/dist/mind/friends/group-context.js +144 -0
  74. package/dist/mind/friends/store-file.js +19 -0
  75. package/dist/mind/friends/trust-explanation.js +74 -0
  76. package/dist/mind/friends/types.js +8 -0
  77. package/dist/mind/memory.js +27 -26
  78. package/dist/mind/obligation-steering.js +221 -0
  79. package/dist/mind/pending.js +76 -9
  80. package/dist/mind/phrases.js +1 -0
  81. package/dist/mind/prompt.js +459 -77
  82. package/dist/mind/token-estimate.js +8 -12
  83. package/dist/nerves/cli-logging.js +15 -2
  84. package/dist/nerves/coverage/run-artifacts.js +1 -1
  85. package/dist/nerves/index.js +12 -0
  86. package/dist/repertoire/ado-client.js +4 -2
  87. package/dist/repertoire/coding/context-pack.js +254 -0
  88. package/dist/repertoire/coding/feedback.js +301 -0
  89. package/dist/repertoire/coding/index.js +4 -1
  90. package/dist/repertoire/coding/manager.js +210 -4
  91. package/dist/repertoire/coding/spawner.js +39 -9
  92. package/dist/repertoire/coding/tools.js +171 -4
  93. package/dist/repertoire/data/ado-endpoints.json +188 -0
  94. package/dist/repertoire/guardrails.js +290 -0
  95. package/dist/repertoire/mcp-client.js +254 -0
  96. package/dist/repertoire/mcp-manager.js +195 -0
  97. package/dist/repertoire/skills.js +3 -26
  98. package/dist/repertoire/tasks/board.js +12 -0
  99. package/dist/repertoire/tasks/index.js +23 -9
  100. package/dist/repertoire/tasks/transitions.js +1 -2
  101. package/dist/repertoire/tools-base.js +925 -250
  102. package/dist/repertoire/tools-bluebubbles.js +93 -0
  103. package/dist/repertoire/tools-teams.js +58 -25
  104. package/dist/repertoire/tools.js +106 -53
  105. package/dist/senses/bluebubbles-client.js +210 -5
  106. package/dist/senses/bluebubbles-entry.js +2 -0
  107. package/dist/senses/bluebubbles-inbound-log.js +109 -0
  108. package/dist/senses/bluebubbles-media.js +339 -0
  109. package/dist/senses/bluebubbles-model.js +12 -4
  110. package/dist/senses/bluebubbles-mutation-log.js +45 -5
  111. package/dist/senses/bluebubbles-runtime-state.js +109 -0
  112. package/dist/senses/bluebubbles-session-cleanup.js +72 -0
  113. package/dist/senses/bluebubbles.js +912 -45
  114. package/dist/senses/cli-layout.js +187 -0
  115. package/dist/senses/cli.js +477 -170
  116. package/dist/senses/continuity.js +94 -0
  117. package/dist/senses/debug-activity.js +154 -0
  118. package/dist/senses/inner-dialog-worker.js +47 -18
  119. package/dist/senses/inner-dialog.js +388 -83
  120. package/dist/senses/pipeline.js +444 -0
  121. package/dist/senses/teams.js +607 -129
  122. package/dist/senses/trust-gate.js +112 -2
  123. package/package.json +14 -3
  124. package/subagents/README.md +4 -70
  125. package/dist/heart/daemon/specialist-session.js +0 -177
  126. package/dist/heart/daemon/subagent-installer.js +0 -134
  127. package/dist/inner-worker-entry.js +0 -4
  128. package/subagents/work-doer.md +0 -233
  129. package/subagents/work-merger.md +0 -624
  130. package/subagents/work-planner.md +0 -373
@@ -3,11 +3,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.toAnthropicMessages = toAnthropicMessages;
7
+ exports.classifyAnthropicError = classifyAnthropicError;
6
8
  exports.createAnthropicProviderRuntime = createAnthropicProviderRuntime;
7
9
  const sdk_1 = __importDefault(require("@anthropic-ai/sdk"));
8
10
  const config_1 = require("../config");
9
11
  const identity_1 = require("../identity");
10
12
  const runtime_1 = require("../../nerves/runtime");
13
+ const streaming_1 = require("../streaming");
14
+ const model_capabilities_1 = require("../model-capabilities");
11
15
  const ANTHROPIC_SETUP_TOKEN_PREFIX = "sk-ant-oat01-";
12
16
  const ANTHROPIC_SETUP_TOKEN_MIN_LENGTH = 80;
13
17
  const ANTHROPIC_OAUTH_BETA_HEADER = "claude-code-20250219,oauth-2025-04-20,fine-grained-tool-streaming-2025-05-14,interleaved-thinking-2025-05-14";
@@ -21,10 +25,10 @@ function getAnthropicSetupTokenInstructions() {
21
25
  const agentName = getAnthropicAgentNameForGuidance();
22
26
  return [
23
27
  "Fix:",
24
- ` 1. Run \`npm run auth:claude-setup-token -- --agent ${agentName}\``,
25
- " (or run `claude setup-token` and paste the token manually)",
28
+ ` 1. Run \`ouro auth --agent ${agentName}\``,
26
29
  ` 2. Open ${getAnthropicSecretsPathForGuidance()}`,
27
30
  " 3. Confirm providers.anthropic.setupToken is set",
31
+ " 4. After reauth, retry the failed ouro command or reconnect this session.",
28
32
  ].join("\n");
29
33
  }
30
34
  function getAnthropicReauthGuidance(reason) {
@@ -92,6 +96,18 @@ function toAnthropicMessages(messages) {
92
96
  if (msg.role === "assistant") {
93
97
  const assistant = msg;
94
98
  const blocks = [];
99
+ // Restore thinking blocks before text/tool_use blocks
100
+ const thinkingBlocks = assistant._thinking_blocks;
101
+ if (thinkingBlocks) {
102
+ for (const tb of thinkingBlocks) {
103
+ if (tb.type === "thinking") {
104
+ blocks.push({ type: "thinking", thinking: tb.thinking, signature: tb.signature });
105
+ }
106
+ else {
107
+ blocks.push({ type: "redacted_thinking", data: tb.data });
108
+ }
109
+ }
110
+ }
95
111
  const text = toAnthropicTextContent(assistant.content);
96
112
  if (text) {
97
113
  blocks.push({ type: "text", text });
@@ -172,6 +188,29 @@ function mergeAnthropicToolArguments(current, partial) {
172
188
  }
173
189
  return current + partial;
174
190
  }
191
+ /* v8 ignore start -- shared network error utility, tested via classification tests @preserve */
192
+ function isNetworkError(error) {
193
+ const code = error.code || "";
194
+ if (["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "EPIPE",
195
+ "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH", "ECONNABORTED"].includes(code))
196
+ return true;
197
+ const msg = error.message || "";
198
+ return msg.includes("fetch failed") || msg.includes("socket hang up") || msg.includes("getaddrinfo");
199
+ }
200
+ /* v8 ignore stop */
201
+ function classifyAnthropicError(error) {
202
+ const status = error.status;
203
+ if (status === 401 || status === 403 || isAnthropicAuthFailure(error))
204
+ return "auth-failure";
205
+ if (status === 429)
206
+ return "rate-limit";
207
+ if (status === 529 || (status && status >= 500))
208
+ return "server-error";
209
+ if (isNetworkError(error))
210
+ return "network-error";
211
+ return "unknown";
212
+ }
213
+ /* v8 ignore start -- auth detection: only called from classifyAnthropicError which always passes Error @preserve */
175
214
  function isAnthropicAuthFailure(error) {
176
215
  if (!(error instanceof Error))
177
216
  return false;
@@ -184,21 +223,18 @@ function isAnthropicAuthFailure(error) {
184
223
  lower.includes("unauthorized") ||
185
224
  lower.includes("invalid api key"));
186
225
  }
187
- function withAnthropicAuthGuidance(error) {
188
- const base = error instanceof Error ? error.message : String(error);
189
- if (isAnthropicAuthFailure(error)) {
190
- return new Error(getAnthropicReauthGuidance(`Anthropic authentication failed (${base}).`));
191
- }
192
- return error instanceof Error ? error : new Error(String(error));
193
- }
226
+ /* v8 ignore stop */
194
227
  async function streamAnthropicMessages(client, model, request) {
195
228
  const { system, messages } = toAnthropicMessages(request.messages);
196
229
  const anthropicTools = toAnthropicTools(request.activeTools);
230
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(model);
231
+ const maxTokens = modelCaps.maxOutputTokens ?? 16384;
197
232
  const params = {
198
233
  model,
199
- max_tokens: 4096,
234
+ max_tokens: maxTokens,
200
235
  messages,
201
236
  stream: true,
237
+ thinking: { type: "adaptive", effort: request.reasoningEffort ?? "medium" },
202
238
  };
203
239
  if (system)
204
240
  params.system = system;
@@ -212,12 +248,15 @@ async function streamAnthropicMessages(client, model, request) {
212
248
  response = await client.messages.create(params, request.signal ? { signal: request.signal } : {});
213
249
  }
214
250
  catch (error) {
215
- throw withAnthropicAuthGuidance(error);
251
+ throw error instanceof Error ? error : new Error(String(error));
216
252
  }
217
253
  let content = "";
218
254
  let streamStarted = false;
219
255
  let usage;
220
256
  const toolCalls = new Map();
257
+ const thinkingBlocks = new Map();
258
+ const redactedBlocks = new Map();
259
+ const answerStreamer = new streaming_1.FinalAnswerStreamer(request.callbacks, request.eagerFinalAnswerStreaming);
221
260
  try {
222
261
  for await (const event of response) {
223
262
  if (request.signal?.aborted)
@@ -225,17 +264,29 @@ async function streamAnthropicMessages(client, model, request) {
225
264
  const eventType = String(event.type ?? "");
226
265
  if (eventType === "content_block_start") {
227
266
  const block = event.content_block;
228
- if (block?.type === "tool_use") {
229
- const index = Number(event.index);
267
+ const index = Number(event.index);
268
+ if (block?.type === "thinking") {
269
+ thinkingBlocks.set(index, { type: "thinking", thinking: "", signature: "" });
270
+ }
271
+ else if (block?.type === "redacted_thinking") {
272
+ redactedBlocks.set(index, { type: "redacted_thinking", data: String(block.data ?? "") });
273
+ }
274
+ else if (block?.type === "tool_use") {
230
275
  const rawInput = block.input;
231
276
  const input = rawInput && typeof rawInput === "object"
232
277
  ? JSON.stringify(rawInput)
233
278
  : "";
279
+ const name = String(block.name ?? "");
234
280
  toolCalls.set(index, {
235
281
  id: String(block.id ?? ""),
236
- name: String(block.name ?? ""),
282
+ name,
237
283
  arguments: input,
238
284
  });
285
+ // Activate eager streaming for sole final_answer tool call
286
+ /* v8 ignore next -- final_answer streaming activation, tested via FinalAnswerStreamer unit tests @preserve */
287
+ if (name === "final_answer" && toolCalls.size === 1) {
288
+ answerStreamer.activate();
289
+ }
239
290
  }
240
291
  continue;
241
292
  }
@@ -257,14 +308,31 @@ async function streamAnthropicMessages(client, model, request) {
257
308
  request.callbacks.onModelStreamStart();
258
309
  streamStarted = true;
259
310
  }
260
- request.callbacks.onReasoningChunk(String(delta?.thinking ?? ""));
311
+ const thinkingText = String(delta?.thinking ?? "");
312
+ request.callbacks.onReasoningChunk(thinkingText);
313
+ const thinkingIndex = Number(event.index);
314
+ const thinkingBlock = thinkingBlocks.get(thinkingIndex);
315
+ if (thinkingBlock)
316
+ thinkingBlock.thinking += thinkingText;
317
+ continue;
318
+ }
319
+ if (deltaType === "signature_delta") {
320
+ const sigIndex = Number(event.index);
321
+ const sigBlock = thinkingBlocks.get(sigIndex);
322
+ if (sigBlock)
323
+ sigBlock.signature += String(delta?.signature ?? "");
261
324
  continue;
262
325
  }
263
326
  if (deltaType === "input_json_delta") {
264
327
  const index = Number(event.index);
265
328
  const existing = toolCalls.get(index);
266
329
  if (existing) {
267
- existing.arguments = mergeAnthropicToolArguments(existing.arguments, String(delta?.partial_json ?? ""));
330
+ const partialJson = String(delta?.partial_json ?? "");
331
+ existing.arguments = mergeAnthropicToolArguments(existing.arguments, partialJson);
332
+ /* v8 ignore next -- final_answer delta streaming, tested via FinalAnswerStreamer unit tests @preserve */
333
+ if (existing.name === "final_answer" && toolCalls.size === 1) {
334
+ answerStreamer.processDelta(partialJson);
335
+ }
268
336
  }
269
337
  continue;
270
338
  }
@@ -286,26 +354,39 @@ async function streamAnthropicMessages(client, model, request) {
286
354
  }
287
355
  }
288
356
  catch (error) {
289
- throw withAnthropicAuthGuidance(error);
357
+ throw error instanceof Error ? error : /* v8 ignore next -- defensive: stream errors are always Error @preserve */ new Error(String(error));
290
358
  }
359
+ // Collect all thinking blocks (regular + redacted) sorted by index to preserve ordering
360
+ const allThinkingIndices = [...thinkingBlocks.keys(), ...redactedBlocks.keys()].sort((a, b) => a - b);
361
+ const outputItems = allThinkingIndices.map((idx) => {
362
+ const tb = thinkingBlocks.get(idx);
363
+ if (tb)
364
+ return tb;
365
+ return redactedBlocks.get(idx);
366
+ });
291
367
  return {
292
368
  content,
293
369
  toolCalls: [...toolCalls.values()],
294
- outputItems: [],
370
+ outputItems,
295
371
  usage,
372
+ finalAnswerStreamed: answerStreamer.streamed,
296
373
  };
297
374
  }
298
- function createAnthropicProviderRuntime() {
375
+ function createAnthropicProviderRuntime(config) {
299
376
  (0, runtime_1.emitNervesEvent)({
300
377
  component: "engine",
301
378
  event: "engine.provider_init",
302
379
  message: "anthropic provider init",
303
380
  meta: { provider: "anthropic" },
304
381
  });
305
- const anthropicConfig = (0, config_1.getAnthropicConfig)();
382
+ const anthropicConfig = config ?? (0, config_1.getAnthropicConfig)();
306
383
  if (!(anthropicConfig.model && anthropicConfig.setupToken)) {
307
384
  throw new Error(getAnthropicReauthGuidance("provider 'anthropic' is selected in agent.json but providers.anthropic.model/setupToken is incomplete in secrets.json."));
308
385
  }
386
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(anthropicConfig.model);
387
+ const capabilities = new Set();
388
+ if (modelCaps.reasoningEffort)
389
+ capabilities.add("reasoning-effort");
309
390
  const credential = resolveAnthropicSetupTokenCredential();
310
391
  const client = new sdk_1.default({
311
392
  authToken: credential.token,
@@ -319,6 +400,8 @@ function createAnthropicProviderRuntime() {
319
400
  id: "anthropic",
320
401
  model: anthropicConfig.model,
321
402
  client,
403
+ capabilities,
404
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
322
405
  resetTurnState(_messages) {
323
406
  // Anthropic request payload is derived from canonical messages each turn.
324
407
  },
@@ -328,5 +411,9 @@ function createAnthropicProviderRuntime() {
328
411
  streamTurn(request) {
329
412
  return streamAnthropicMessages(client, anthropicConfig.model, request);
330
413
  },
414
+ /* v8 ignore next 3 -- delegation: classification logic tested via classifyAnthropicError @preserve */
415
+ classifyError(error) {
416
+ return classifyAnthropicError(error);
417
+ },
331
418
  };
332
419
  }
@@ -1,35 +1,137 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
2
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.classifyAzureError = classifyAzureError;
37
+ exports.createAzureTokenProvider = createAzureTokenProvider;
3
38
  exports.createAzureProviderRuntime = createAzureProviderRuntime;
4
39
  const openai_1 = require("openai");
5
40
  const config_1 = require("../config");
6
41
  const runtime_1 = require("../../nerves/runtime");
7
42
  const streaming_1 = require("../streaming");
8
- function createAzureProviderRuntime() {
43
+ const model_capabilities_1 = require("../model-capabilities");
44
+ const COGNITIVE_SERVICES_SCOPE = "https://cognitiveservices.azure.com/.default";
45
+ /* v8 ignore start -- shared network error utility, tested via classification tests @preserve */
46
+ function isNetworkError(error) {
47
+ const code = error.code || "";
48
+ if (["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "EPIPE",
49
+ "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH", "ECONNABORTED"].includes(code))
50
+ return true;
51
+ const msg = error.message || "";
52
+ return msg.includes("fetch failed") || msg.includes("socket hang up") || msg.includes("getaddrinfo");
53
+ }
54
+ /* v8 ignore stop */
55
+ function classifyAzureError(error) {
56
+ const status = error.status;
57
+ if (status === 401 || status === 403)
58
+ return "auth-failure";
59
+ if (status === 429)
60
+ return "rate-limit";
61
+ if (status && status >= 500)
62
+ return "server-error";
63
+ if (isNetworkError(error))
64
+ return "network-error";
65
+ return "unknown";
66
+ }
67
+ // @azure/identity is imported dynamically (below) rather than at the top level
68
+ // because it's a heavy package (~30+ transitive deps) and we only need it when
69
+ // using the managed-identity auth path. API-key users and other providers
70
+ // shouldn't pay the cold-start cost.
71
+ function createAzureTokenProvider(managedIdentityClientId) {
72
+ let credential = null;
73
+ return async () => {
74
+ try {
75
+ if (!credential) {
76
+ const { DefaultAzureCredential } = await Promise.resolve().then(() => __importStar(require("@azure/identity")));
77
+ const credentialOptions = managedIdentityClientId
78
+ ? { managedIdentityClientId }
79
+ : undefined;
80
+ credential = new DefaultAzureCredential(credentialOptions);
81
+ }
82
+ const tokenResponse = await credential.getToken(COGNITIVE_SERVICES_SCOPE);
83
+ return tokenResponse.token;
84
+ }
85
+ catch (err) {
86
+ const detail = err instanceof Error ? err.message : String(err);
87
+ throw new Error(`Azure OpenAI authentication failed: ${detail}\n` +
88
+ "To fix this, either:\n" +
89
+ " 1. Set providers.azure.apiKey in secrets.json, or\n" +
90
+ " 2. Run 'az login' to authenticate with your Azure account (for local dev), or\n" +
91
+ " 3. Attach a managed identity to your App Service and set providers.azure.managedIdentityClientId in secrets.json (for deployed environments)");
92
+ }
93
+ };
94
+ }
95
+ function createAzureProviderRuntime(config) {
96
+ const azureConfig = config ?? (0, config_1.getAzureConfig)();
97
+ const useApiKey = !!azureConfig.apiKey;
98
+ const authMethod = useApiKey ? "key" : "managed-identity";
9
99
  (0, runtime_1.emitNervesEvent)({
10
100
  component: "engine",
11
101
  event: "engine.provider_init",
12
102
  message: "azure provider init",
13
- meta: { provider: "azure" },
103
+ meta: { provider: "azure", authMethod },
14
104
  });
15
- const azureConfig = (0, config_1.getAzureConfig)();
16
- if (!(azureConfig.apiKey && azureConfig.endpoint && azureConfig.deployment && azureConfig.modelName)) {
105
+ if (!(azureConfig.endpoint && azureConfig.deployment && azureConfig.modelName)) {
17
106
  throw new Error("provider 'azure' is selected in agent.json but providers.azure is incomplete in secrets.json.");
18
107
  }
19
- const client = new openai_1.AzureOpenAI({
20
- apiKey: azureConfig.apiKey,
108
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(azureConfig.modelName);
109
+ const capabilities = new Set();
110
+ if (modelCaps.reasoningEffort)
111
+ capabilities.add("reasoning-effort");
112
+ const clientOptions = {
21
113
  endpoint: azureConfig.endpoint.replace(/\/openai.*$/, ""),
22
114
  deployment: azureConfig.deployment,
23
115
  apiVersion: azureConfig.apiVersion,
24
116
  timeout: 30000,
25
117
  maxRetries: 0,
26
- });
118
+ };
119
+ if (useApiKey) {
120
+ clientOptions.apiKey = azureConfig.apiKey;
121
+ }
122
+ else {
123
+ const managedIdentityClientId = azureConfig.managedIdentityClientId || undefined;
124
+ clientOptions.azureADTokenProvider = createAzureTokenProvider(managedIdentityClientId);
125
+ }
126
+ const client = new openai_1.AzureOpenAI(clientOptions);
27
127
  let nativeInput = null;
28
128
  let nativeInstructions = "";
29
129
  return {
30
130
  id: "azure",
31
131
  model: azureConfig.modelName,
32
132
  client,
133
+ capabilities,
134
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
33
135
  resetTurnState(messages) {
34
136
  const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
35
137
  nativeInput = input;
@@ -48,7 +150,7 @@ function createAzureProviderRuntime() {
48
150
  input: nativeInput,
49
151
  instructions: nativeInstructions,
50
152
  tools: (0, streaming_1.toResponsesTools)(request.activeTools),
51
- reasoning: { effort: "medium", summary: "detailed" },
153
+ reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
52
154
  stream: true,
53
155
  store: false,
54
156
  include: ["reasoning.encrypted_content"],
@@ -57,10 +159,14 @@ function createAzureProviderRuntime() {
57
159
  params.metadata = { trace_id: request.traceId };
58
160
  if (request.toolChoiceRequired)
59
161
  params.tool_choice = "required";
60
- const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal);
162
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
61
163
  for (const item of result.outputItems)
62
164
  nativeInput.push(item);
63
165
  return result;
64
166
  },
167
+ /* v8 ignore next 3 -- delegation: classification logic tested via classifyAzureError @preserve */
168
+ classifyError(error) {
169
+ return classifyAzureError(error);
170
+ },
65
171
  };
66
172
  }
@@ -0,0 +1,157 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.classifyGithubCopilotError = classifyGithubCopilotError;
7
+ exports.createGithubCopilotProviderRuntime = createGithubCopilotProviderRuntime;
8
+ const openai_1 = __importDefault(require("openai"));
9
+ const config_1 = require("../config");
10
+ const runtime_1 = require("../../nerves/runtime");
11
+ const streaming_1 = require("../streaming");
12
+ const model_capabilities_1 = require("../model-capabilities");
13
+ /* v8 ignore start -- duplicated from shared provider utils, tested there @preserve */
14
+ function isNetworkError(error) {
15
+ const code = error.code || "";
16
+ if (["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "EPIPE",
17
+ "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH", "ECONNABORTED"].includes(code))
18
+ return true;
19
+ const msg = error.message || "";
20
+ return msg.includes("fetch failed") || msg.includes("socket hang up") || msg.includes("getaddrinfo");
21
+ }
22
+ /* v8 ignore stop */
23
+ /* v8 ignore start -- duplicated classification pattern, tested via provider unit tests @preserve */
24
+ function classifyGithubCopilotError(error) {
25
+ const status = error.status;
26
+ if (status === 401 || status === 403)
27
+ return "auth-failure";
28
+ if (status === 429)
29
+ return "rate-limit";
30
+ if (status && status >= 500)
31
+ return "server-error";
32
+ if (isNetworkError(error))
33
+ return "network-error";
34
+ return "unknown";
35
+ }
36
+ /* v8 ignore stop */
37
+ function createGithubCopilotProviderRuntime(injectedConfig) {
38
+ (0, runtime_1.emitNervesEvent)({
39
+ component: "engine",
40
+ event: "engine.provider_init",
41
+ message: "github-copilot provider init",
42
+ meta: { provider: "github-copilot" },
43
+ });
44
+ const config = injectedConfig ?? (0, config_1.getGithubCopilotConfig)();
45
+ if (!config.githubToken) {
46
+ throw new Error("provider 'github-copilot' is selected in agent.json but providers.github-copilot.githubToken is missing in secrets.json.");
47
+ }
48
+ if (!config.baseUrl) {
49
+ throw new Error("provider 'github-copilot' is selected in agent.json but providers.github-copilot.baseUrl is missing in secrets.json.");
50
+ }
51
+ const isCompletionsModel = config.model.startsWith("claude");
52
+ const modelCaps = (0, model_capabilities_1.getModelCapabilities)(config.model);
53
+ const capabilities = new Set();
54
+ /* v8 ignore next -- branch: capability detection tested via unit test @preserve */
55
+ if (modelCaps.reasoningEffort)
56
+ capabilities.add("reasoning-effort");
57
+ const client = new openai_1.default({
58
+ apiKey: config.githubToken,
59
+ baseURL: config.baseUrl,
60
+ timeout: 30000,
61
+ maxRetries: 0,
62
+ });
63
+ if (isCompletionsModel) {
64
+ // Chat completions path (Claude models via Copilot)
65
+ return {
66
+ id: "github-copilot",
67
+ model: config.model,
68
+ client,
69
+ capabilities,
70
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
71
+ resetTurnState(_messages) {
72
+ // No provider-owned turn state for chat-completions path.
73
+ },
74
+ appendToolOutput(_callId, _output) {
75
+ // Chat-completions providers rely on canonical messages only.
76
+ },
77
+ /* v8 ignore start -- streamTurn: tested via mock assertions in github-copilot.test.ts @preserve */
78
+ async streamTurn(request) {
79
+ const params = {
80
+ messages: request.messages,
81
+ tools: request.activeTools,
82
+ stream: true,
83
+ };
84
+ if (this.model)
85
+ params.model = this.model;
86
+ if (request.traceId)
87
+ params.metadata = { trace_id: request.traceId };
88
+ if (request.toolChoiceRequired)
89
+ params.tool_choice = "required";
90
+ try {
91
+ return await (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
92
+ }
93
+ catch (error) {
94
+ throw error instanceof Error ? error : new Error(String(error));
95
+ }
96
+ },
97
+ /* v8 ignore stop */
98
+ /* v8 ignore next 3 -- delegation: classification logic tested via classifyGithubCopilotError @preserve */
99
+ classifyError(error) {
100
+ return classifyGithubCopilotError(error);
101
+ },
102
+ };
103
+ }
104
+ // Responses API path (GPT models via Copilot)
105
+ let nativeInput = null;
106
+ let nativeInstructions = "";
107
+ return {
108
+ id: "github-copilot",
109
+ model: config.model,
110
+ client,
111
+ capabilities,
112
+ supportedReasoningEfforts: modelCaps.reasoningEffort,
113
+ /* v8 ignore start -- responses path: tested via mock assertions in github-copilot.test.ts @preserve */
114
+ resetTurnState(messages) {
115
+ const { instructions, input } = (0, streaming_1.toResponsesInput)(messages);
116
+ nativeInput = input;
117
+ nativeInstructions = instructions;
118
+ },
119
+ appendToolOutput(callId, output) {
120
+ if (!nativeInput)
121
+ return;
122
+ nativeInput.push({ type: "function_call_output", call_id: callId, output });
123
+ },
124
+ async streamTurn(request) {
125
+ if (!nativeInput)
126
+ this.resetTurnState(request.messages);
127
+ const params = {
128
+ model: this.model,
129
+ input: nativeInput,
130
+ instructions: nativeInstructions,
131
+ tools: (0, streaming_1.toResponsesTools)(request.activeTools),
132
+ reasoning: { effort: request.reasoningEffort ?? "medium", summary: "detailed" },
133
+ stream: true,
134
+ store: false,
135
+ include: ["reasoning.encrypted_content"],
136
+ };
137
+ if (request.traceId)
138
+ params.metadata = { trace_id: request.traceId };
139
+ if (request.toolChoiceRequired)
140
+ params.tool_choice = "required";
141
+ try {
142
+ const result = await (0, streaming_1.streamResponsesApi)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
143
+ for (const item of result.outputItems)
144
+ nativeInput.push(item);
145
+ return result;
146
+ }
147
+ catch (error) {
148
+ throw error instanceof Error ? error : new Error(String(error));
149
+ }
150
+ },
151
+ /* v8 ignore stop */
152
+ /* v8 ignore next 3 -- delegation: classification logic tested via classifyGithubCopilotError @preserve */
153
+ classifyError(error) {
154
+ return classifyGithubCopilotError(error);
155
+ },
156
+ };
157
+ }
@@ -3,22 +3,48 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.classifyMinimaxError = classifyMinimaxError;
6
7
  exports.createMinimaxProviderRuntime = createMinimaxProviderRuntime;
7
8
  const openai_1 = __importDefault(require("openai"));
8
9
  const config_1 = require("../config");
9
10
  const runtime_1 = require("../../nerves/runtime");
11
+ /* v8 ignore start -- shared network error utility, tested via classification tests @preserve */
12
+ function isNetworkError(error) {
13
+ const code = error.code || "";
14
+ if (["ECONNRESET", "ECONNREFUSED", "ENOTFOUND", "ETIMEDOUT", "EPIPE",
15
+ "EAI_AGAIN", "EHOSTUNREACH", "ENETUNREACH", "ECONNABORTED"].includes(code))
16
+ return true;
17
+ const msg = error.message || "";
18
+ return msg.includes("fetch failed") || msg.includes("socket hang up") || msg.includes("getaddrinfo");
19
+ }
20
+ /* v8 ignore stop */
21
+ function classifyMinimaxError(error) {
22
+ const status = error.status;
23
+ if (status === 401 || status === 403)
24
+ return "auth-failure";
25
+ if (status === 429)
26
+ return "rate-limit";
27
+ if (status && status >= 500)
28
+ return "server-error";
29
+ if (isNetworkError(error))
30
+ return "network-error";
31
+ return "unknown";
32
+ }
10
33
  const streaming_1 = require("../streaming");
11
- function createMinimaxProviderRuntime() {
34
+ const model_capabilities_1 = require("../model-capabilities");
35
+ function createMinimaxProviderRuntime(config) {
12
36
  (0, runtime_1.emitNervesEvent)({
13
37
  component: "engine",
14
38
  event: "engine.provider_init",
15
39
  message: "minimax provider init",
16
40
  meta: { provider: "minimax" },
17
41
  });
18
- const minimaxConfig = (0, config_1.getMinimaxConfig)();
42
+ const minimaxConfig = config ?? (0, config_1.getMinimaxConfig)();
19
43
  if (!minimaxConfig.apiKey) {
20
44
  throw new Error("provider 'minimax' is selected in agent.json but providers.minimax.apiKey is missing in secrets.json.");
21
45
  }
46
+ // Registry consulted; MiniMax models return empty defaults (no capabilities to derive)
47
+ (0, model_capabilities_1.getModelCapabilities)(minimaxConfig.model);
22
48
  const client = new openai_1.default({
23
49
  apiKey: minimaxConfig.apiKey,
24
50
  baseURL: "https://api.minimaxi.chat/v1",
@@ -29,6 +55,7 @@ function createMinimaxProviderRuntime() {
29
55
  id: "minimax",
30
56
  model: minimaxConfig.model,
31
57
  client,
58
+ capabilities: new Set(),
32
59
  resetTurnState(_messages) {
33
60
  // No provider-owned turn state for chat-completions providers.
34
61
  },
@@ -47,7 +74,10 @@ function createMinimaxProviderRuntime() {
47
74
  params.metadata = { trace_id: request.traceId };
48
75
  if (request.toolChoiceRequired)
49
76
  params.tool_choice = "required";
50
- return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal);
77
+ return (0, streaming_1.streamChatCompletion)(this.client, params, request.callbacks, request.signal, request.eagerFinalAnswerStreaming);
78
+ },
79
+ classifyError(error) {
80
+ return classifyMinimaxError(error);
51
81
  },
52
82
  };
53
83
  }