@ouro.bot/cli 0.1.0-alpha.122 → 0.1.0-alpha.124

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/changelog.json CHANGED
@@ -1,6 +1,18 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.124",
6
+ "changes": [
7
+ "Anthropic OAuth tokens now auto-refresh before expiry. The provider checks token freshness before each turn, exchanges the refresh token for a new access token, and persists the result. No more 400 errors from expired setup tokens."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.123",
12
+ "changes": [
13
+ "Fixed Anthropic API request format: effort parameter moved from thinking object to output_config. Was causing 400 errors that masked real model access restrictions."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.122",
6
18
  "changes": [
@@ -65,6 +65,8 @@ const DEFAULT_SECRETS_TEMPLATE = {
65
65
  anthropic: {
66
66
  model: "claude-opus-4-6",
67
67
  setupToken: "",
68
+ refreshToken: "",
69
+ expiresAt: 0,
68
70
  },
69
71
  "openai-codex": {
70
72
  model: "gpt-5.4",
@@ -0,0 +1,163 @@
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
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.needsRefresh = needsRefresh;
37
+ exports.refreshAnthropicToken = refreshAnthropicToken;
38
+ exports.persistTokenState = persistTokenState;
39
+ exports.ensureFreshToken = ensureFreshToken;
40
+ /* v8 ignore start -- OAuth token lifecycle: requires live API calls, tested via integration @preserve */
41
+ const fs = __importStar(require("fs"));
42
+ const runtime_1 = require("../../nerves/runtime");
43
+ const identity_1 = require("../identity");
44
+ const OAUTH_TOKEN_ENDPOINT = "https://console.anthropic.com/v1/oauth/token";
45
+ const OAUTH_CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
46
+ const REFRESH_MARGIN_MS = 5 * 60 * 1000; // refresh 5 minutes before expiry
47
+ /**
48
+ * Check if the Anthropic OAuth token needs refreshing.
49
+ * Returns true if no expiresAt is set (legacy token) or if within 5 min of expiry.
50
+ */
51
+ function needsRefresh(expiresAt) {
52
+ if (!expiresAt)
53
+ return true; // legacy token with no expiry — always try refresh
54
+ return Date.now() > expiresAt - REFRESH_MARGIN_MS;
55
+ }
56
+ /**
57
+ * Refresh an Anthropic OAuth access token using the refresh token.
58
+ * Returns the new token state or null if refresh fails.
59
+ */
60
+ async function refreshAnthropicToken(refreshToken, fetchImpl = fetch) {
61
+ try {
62
+ const response = await fetchImpl(OAUTH_TOKEN_ENDPOINT, {
63
+ method: "POST",
64
+ headers: { "Content-Type": "application/json" },
65
+ body: JSON.stringify({
66
+ grant_type: "refresh_token",
67
+ refresh_token: refreshToken,
68
+ client_id: OAUTH_CLIENT_ID,
69
+ }),
70
+ });
71
+ if (!response.ok) {
72
+ (0, runtime_1.emitNervesEvent)({
73
+ level: "warn",
74
+ component: "engine",
75
+ event: "engine.anthropic_token_refresh_failed",
76
+ message: `token refresh failed: ${response.status}`,
77
+ meta: { status: response.status },
78
+ });
79
+ return null;
80
+ }
81
+ const json = await response.json();
82
+ if (!json.access_token) {
83
+ (0, runtime_1.emitNervesEvent)({
84
+ level: "warn",
85
+ component: "engine",
86
+ event: "engine.anthropic_token_refresh_failed",
87
+ message: "token refresh returned no access_token",
88
+ meta: {},
89
+ });
90
+ return null;
91
+ }
92
+ const state = {
93
+ accessToken: json.access_token,
94
+ refreshToken: json.refresh_token ?? refreshToken, // keep old if not returned
95
+ expiresAt: Date.now() + (json.expires_in ?? 28800) * 1000, // default 8h
96
+ };
97
+ (0, runtime_1.emitNervesEvent)({
98
+ component: "engine",
99
+ event: "engine.anthropic_token_refreshed",
100
+ message: "anthropic OAuth token refreshed",
101
+ meta: { expiresAt: new Date(state.expiresAt).toISOString() },
102
+ });
103
+ return state;
104
+ }
105
+ catch (error) {
106
+ (0, runtime_1.emitNervesEvent)({
107
+ level: "warn",
108
+ component: "engine",
109
+ event: "engine.anthropic_token_refresh_error",
110
+ message: "token refresh threw",
111
+ meta: { error: error instanceof Error ? error.message : String(error) },
112
+ });
113
+ return null;
114
+ }
115
+ }
116
+ /**
117
+ * Persist refreshed token state back to secrets.json.
118
+ */
119
+ function persistTokenState(agentName, state) {
120
+ try {
121
+ const secretsPath = (0, identity_1.getAgentSecretsPath)(agentName);
122
+ const raw = fs.readFileSync(secretsPath, "utf-8");
123
+ const secrets = JSON.parse(raw);
124
+ secrets.providers = secrets.providers ?? {};
125
+ secrets.providers.anthropic = secrets.providers.anthropic ?? {};
126
+ secrets.providers.anthropic.setupToken = state.accessToken;
127
+ secrets.providers.anthropic.refreshToken = state.refreshToken;
128
+ secrets.providers.anthropic.expiresAt = state.expiresAt;
129
+ fs.writeFileSync(secretsPath, JSON.stringify(secrets, null, 2) + "\n", "utf-8");
130
+ /* v8 ignore start -- defensive: persistence failure must not crash the provider @preserve */
131
+ }
132
+ catch (error) {
133
+ (0, runtime_1.emitNervesEvent)({
134
+ level: "warn",
135
+ component: "engine",
136
+ event: "engine.anthropic_token_persist_error",
137
+ message: "failed to persist refreshed token",
138
+ meta: { error: error instanceof Error ? error.message : String(error) },
139
+ });
140
+ }
141
+ /* v8 ignore stop */
142
+ }
143
+ /**
144
+ * Ensure the Anthropic token is fresh. If expired, refresh and persist.
145
+ * Returns the current valid access token, or null if refresh failed and
146
+ * the existing token is expired.
147
+ */
148
+ async function ensureFreshToken(currentToken, refreshToken, expiresAt, agentName, fetchImpl) {
149
+ if (!needsRefresh(expiresAt)) {
150
+ return currentToken; // still fresh
151
+ }
152
+ if (!refreshToken) {
153
+ // No refresh token — use the current token as-is (may be expired)
154
+ return currentToken;
155
+ }
156
+ const newState = await refreshAnthropicToken(refreshToken, fetchImpl);
157
+ if (!newState) {
158
+ return currentToken; // refresh failed — try the old token
159
+ }
160
+ persistTokenState(agentName, newState);
161
+ return newState.accessToken;
162
+ }
163
+ /* v8 ignore stop */
@@ -1,4 +1,37 @@
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
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -234,7 +267,8 @@ async function streamAnthropicMessages(client, model, request) {
234
267
  max_tokens: maxTokens,
235
268
  messages,
236
269
  stream: true,
237
- thinking: { type: "adaptive", effort: request.reasoningEffort ?? "medium" },
270
+ thinking: { type: "adaptive" },
271
+ output_config: { effort: request.reasoningEffort ?? "medium" },
238
272
  };
239
273
  if (system)
240
274
  params.system = system;
@@ -388,18 +422,43 @@ function createAnthropicProviderRuntime(config) {
388
422
  if (modelCaps.reasoningEffort)
389
423
  capabilities.add("reasoning-effort");
390
424
  const credential = resolveAnthropicSetupTokenCredential();
391
- const client = new sdk_1.default({
392
- authToken: credential.token,
393
- timeout: 30000,
394
- maxRetries: 0,
395
- defaultHeaders: {
396
- "anthropic-beta": ANTHROPIC_OAUTH_BETA_HEADER,
397
- },
398
- });
425
+ const fullConfig = config ?? (0, config_1.getAnthropicConfig)();
426
+ const refreshToken = fullConfig.refreshToken;
427
+ const expiresAt = fullConfig.expiresAt;
428
+ function createClient(token) {
429
+ return new sdk_1.default({
430
+ authToken: token,
431
+ timeout: 30000,
432
+ maxRetries: 0,
433
+ defaultHeaders: {
434
+ "anthropic-beta": ANTHROPIC_OAUTH_BETA_HEADER,
435
+ },
436
+ });
437
+ }
438
+ let currentToken = credential.token;
439
+ let client = createClient(currentToken);
440
+ /* v8 ignore start -- token refresh: dynamic import + ensureFreshToken, tested via integration @preserve */
441
+ async function ensureClient() {
442
+ try {
443
+ const { ensureFreshToken } = await Promise.resolve().then(() => __importStar(require("./anthropic-token")));
444
+ const { getAgentName } = await Promise.resolve().then(() => __importStar(require("../identity")));
445
+ const freshToken = await ensureFreshToken(currentToken, refreshToken, expiresAt, getAgentName());
446
+ if (freshToken !== currentToken) {
447
+ currentToken = freshToken;
448
+ client = createClient(freshToken);
449
+ }
450
+ }
451
+ catch {
452
+ // refresh failed — use existing client
453
+ }
454
+ return client;
455
+ }
456
+ /* v8 ignore stop */
399
457
  return {
400
458
  id: "anthropic",
401
459
  model: anthropicConfig.model,
402
- client,
460
+ /* v8 ignore next -- getter: returns mutable client ref @preserve */
461
+ get client() { return client; },
403
462
  capabilities,
404
463
  supportedReasoningEfforts: modelCaps.reasoningEffort,
405
464
  resetTurnState(_messages) {
@@ -408,8 +467,9 @@ function createAnthropicProviderRuntime(config) {
408
467
  appendToolOutput(_callId, _output) {
409
468
  // Anthropic uses canonical messages for tool_result tracking.
410
469
  },
411
- streamTurn(request) {
412
- return streamAnthropicMessages(client, anthropicConfig.model, request);
470
+ async streamTurn(request) {
471
+ const freshClient = await ensureClient();
472
+ return streamAnthropicMessages(freshClient, anthropicConfig.model, request);
413
473
  },
414
474
  /* v8 ignore next 3 -- delegation: classification logic tested via classifyAnthropicError @preserve */
415
475
  classifyError(error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.122",
3
+ "version": "0.1.0-alpha.124",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",