agentxchain 2.44.0 → 2.45.0

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/README.md CHANGED
@@ -24,7 +24,7 @@ Legacy IDE-window coordination is still shipped as a compatibility mode for team
24
24
  See governance before you scaffold a real repo:
25
25
 
26
26
  ```bash
27
- npx agentxchain demo
27
+ npx --yes -p agentxchain@latest -c "agentxchain demo"
28
28
  ```
29
29
 
30
30
  Requires Node.js 18.17+ or 20.5+ and `git`. The demo creates a temporary governed repo, runs a full PM -> Dev -> QA lifecycle through the real runner interface, shows gates/decisions/objections, and removes the temp workspace when finished. No API keys, config edits, or manual turn authoring required.
@@ -33,12 +33,13 @@ Requires Node.js 18.17+ or 20.5+ and `git`. The demo creates a temporary governe
33
33
 
34
34
  ```bash
35
35
  npm install -g agentxchain
36
+ agentxchain --version
36
37
  ```
37
38
 
38
- Or run without installing:
39
+ For a zero-install one-off command, use the package-bound form:
39
40
 
40
41
  ```bash
41
- npx agentxchain init --governed --dir my-agentxchain-project -y
42
+ npx --yes -p agentxchain@latest -c "agentxchain demo"
42
43
  ```
43
44
 
44
45
  ## Testing
@@ -62,7 +63,7 @@ Duplicate execution remains intentional for the current 36-file slice until a la
62
63
  ### Governed workflow
63
64
 
64
65
  ```bash
65
- npx agentxchain init --governed --dir my-agentxchain-project -y
66
+ agentxchain init --governed --dir my-agentxchain-project -y
66
67
  cd my-agentxchain-project
67
68
  git init
68
69
  git add -A
@@ -74,13 +75,13 @@ agentxchain step --role pm
74
75
  The default governed dev runtime is `claude --print --dangerously-skip-permissions` with stdin prompt delivery. The non-interactive governed path needs write access, so do not pretend bare `claude --print` is sufficient for unattended implementation turns. If your local coding agent uses a different launch contract, set it during scaffold creation:
75
76
 
76
77
  ```bash
77
- npx agentxchain init --governed --dir my-agentxchain-project --dev-command ./scripts/dev-agent.sh --dev-prompt-transport dispatch_bundle_only -y
78
+ agentxchain init --governed --dir my-agentxchain-project --dev-command ./scripts/dev-agent.sh --dev-prompt-transport dispatch_bundle_only -y
78
79
  ```
79
80
 
80
81
  If you want template-specific planning artifacts from day one:
81
82
 
82
83
  ```bash
83
- npx agentxchain init --governed --template api-service --dir my-agentxchain-project -y
84
+ agentxchain init --governed --template api-service --dir my-agentxchain-project -y
84
85
  ```
85
86
 
86
87
  Built-in governed templates:
@@ -119,8 +120,8 @@ Default governed scaffolding configures QA as `api_proxy` with `ANTHROPIC_API_KE
119
120
  For initiatives spanning multiple governed repos, use the coordinator to add cross-repo sequencing and shared gates:
120
121
 
121
122
  ```bash
122
- npx agentxchain init --governed --template api-service --dir repos/backend -y
123
- npx agentxchain init --governed --template web-app --dir repos/frontend -y
123
+ agentxchain init --governed --template api-service --dir repos/backend -y
124
+ agentxchain init --governed --template web-app --dir repos/frontend -y
124
125
  agentxchain multi init
125
126
  agentxchain multi step --json
126
127
  ```
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentxchain",
3
- "version": "2.44.0",
3
+ "version": "2.45.0",
4
4
  "description": "CLI for AgentXchain — governed multi-agent software delivery",
5
5
  "type": "module",
6
6
  "bin": {
@@ -23,7 +23,7 @@
23
23
  * All error returns include a `classified` ApiProxyError object with
24
24
  * error_class, recovery instructions, and retryable flag.
25
25
  *
26
- * Supported providers: "anthropic", "openai", "google"
26
+ * Supported providers: "anthropic", "openai", "google", "ollama"
27
27
  */
28
28
 
29
29
  import { readFileSync, writeFileSync, existsSync, mkdirSync, rmSync } from 'fs';
@@ -51,6 +51,7 @@ const PROVIDER_ENDPOINTS = {
51
51
  anthropic: 'https://api.anthropic.com/v1/messages',
52
52
  openai: 'https://api.openai.com/v1/chat/completions',
53
53
  google: 'https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent',
54
+ ollama: 'http://localhost:11434/v1/chat/completions',
54
55
  };
55
56
 
56
57
  // Bundled cost rates per million tokens (USD).
@@ -161,6 +162,22 @@ const PROVIDER_ERROR_MAPS = {
161
162
  { provider_error_type: 'INTERNAL', http_status: 500, error_class: 'unknown_api_error', retryable: true },
162
163
  ],
163
164
  },
165
+ // Ollama uses OpenAI-compatible error structure
166
+ ollama: {
167
+ extractErrorType(body) {
168
+ return typeof body?.error?.type === 'string' ? body.error.type : null;
169
+ },
170
+ extractErrorCode(body) {
171
+ return typeof body?.error?.code === 'string' ? body.error.code : null;
172
+ },
173
+ mappings: [
174
+ { provider_error_code: 'invalid_api_key', http_status: 401, error_class: 'auth_failure', retryable: false },
175
+ { provider_error_code: 'model_not_found', http_status: 404, error_class: 'model_not_found', retryable: false },
176
+ { provider_error_type: 'invalid_request_error', http_status: 400, body_pattern: /context|token.*limit|too.many.tokens/i, error_class: 'context_overflow', retryable: false },
177
+ { provider_error_type: 'invalid_request_error', http_status: 400, error_class: 'invalid_request', retryable: false },
178
+ { provider_error_type: 'rate_limit_error', http_status: 429, error_class: 'rate_limited', retryable: true },
179
+ ],
180
+ },
164
181
  };
165
182
 
166
183
  // ── Error classification ──────────────────────────────────────────────────────
@@ -465,7 +482,7 @@ function usageFromTelemetry(provider, model, usage, config) {
465
482
  let inputTokens = 0;
466
483
  let outputTokens = 0;
467
484
 
468
- if (provider === 'openai') {
485
+ if (provider === 'openai' || provider === 'ollama') {
469
486
  inputTokens = Number.isFinite(usage.prompt_tokens) ? usage.prompt_tokens : 0;
470
487
  outputTokens = Number.isFinite(usage.completion_tokens) ? usage.completion_tokens : 0;
471
488
  } else if (provider === 'google') {
@@ -811,9 +828,10 @@ export async function dispatchApiProxy(root, state, config, options = {}) {
811
828
  const provider = runtime.provider;
812
829
  const model = runtime.model;
813
830
  const authEnv = runtime.auth_env;
814
- const apiKey = process.env[authEnv];
831
+ const apiKey = authEnv ? process.env[authEnv] : null;
815
832
 
816
- if (!apiKey) {
833
+ // Auth is required for cloud providers, optional for local providers (ollama)
834
+ if (!apiKey && authEnv) {
817
835
  const classified = classifyError(
818
836
  'missing_credentials',
819
837
  `Environment variable "${authEnv}" is not set — required for api_proxy`,
@@ -1124,6 +1142,15 @@ function buildOpenAiRequest(promptMd, contextMd, model, maxOutputTokens) {
1124
1142
  };
1125
1143
  }
1126
1144
 
1145
+ function buildOllamaRequest(promptMd, contextMd, model, maxOutputTokens) {
1146
+ const openAiCompatible = buildOpenAiRequest(promptMd, contextMd, model, maxOutputTokens);
1147
+ return {
1148
+ ...openAiCompatible,
1149
+ max_tokens: maxOutputTokens,
1150
+ max_completion_tokens: undefined,
1151
+ };
1152
+ }
1153
+
1127
1154
  function buildGoogleHeaders(_apiKey) {
1128
1155
  // Google Gemini uses API key as a query parameter, not a header
1129
1156
  return {
@@ -1203,6 +1230,14 @@ function extractGoogleTurnResult(responseData) {
1203
1230
  return extraction;
1204
1231
  }
1205
1232
 
1233
+ function buildOllamaHeaders(apiKey) {
1234
+ const headers = { 'Content-Type': 'application/json' };
1235
+ if (apiKey) {
1236
+ headers['Authorization'] = `Bearer ${apiKey}`;
1237
+ }
1238
+ return headers;
1239
+ }
1240
+
1206
1241
  function buildProviderHeaders(provider, apiKey) {
1207
1242
  if (provider === 'openai') {
1208
1243
  return buildOpenAiHeaders(apiKey);
@@ -1210,6 +1245,9 @@ function buildProviderHeaders(provider, apiKey) {
1210
1245
  if (provider === 'google') {
1211
1246
  return buildGoogleHeaders(apiKey);
1212
1247
  }
1248
+ if (provider === 'ollama') {
1249
+ return buildOllamaHeaders(apiKey);
1250
+ }
1213
1251
  return buildAnthropicHeaders(apiKey);
1214
1252
  }
1215
1253
 
@@ -1217,6 +1255,9 @@ function buildProviderRequest(provider, promptMd, contextMd, model, maxOutputTok
1217
1255
  if (provider === 'openai') {
1218
1256
  return buildOpenAiRequest(promptMd, contextMd, model, maxOutputTokens);
1219
1257
  }
1258
+ if (provider === 'ollama') {
1259
+ return buildOllamaRequest(promptMd, contextMd, model, maxOutputTokens);
1260
+ }
1220
1261
  if (provider === 'google') {
1221
1262
  return buildGoogleRequest(promptMd, contextMd, model, maxOutputTokens);
1222
1263
  }
@@ -1304,7 +1345,7 @@ function extractOpenAiTurnResult(responseData) {
1304
1345
  }
1305
1346
 
1306
1347
  function extractTurnResult(responseData, provider = 'anthropic') {
1307
- if (provider === 'openai') {
1348
+ if (provider === 'openai' || provider === 'ollama') {
1308
1349
  return extractOpenAiTurnResult(responseData);
1309
1350
  }
1310
1351
  if (provider === 'google') {
@@ -1324,10 +1365,17 @@ export {
1324
1365
  extractTurnResult,
1325
1366
  buildAnthropicRequest,
1326
1367
  buildOpenAiRequest,
1368
+ buildOllamaRequest,
1327
1369
  buildGoogleRequest,
1370
+ buildOllamaHeaders,
1371
+ buildProviderHeaders,
1372
+ buildProviderRequest,
1328
1373
  classifyError,
1329
1374
  classifyHttpError,
1375
+ DEFAULT_RETRY_POLICY,
1330
1376
  BUNDLED_COST_RATES,
1331
1377
  BUNDLED_COST_RATES as COST_RATES, // backward compat alias
1332
1378
  getCostRates,
1379
+ PROVIDER_ENDPOINTS,
1380
+ RETRYABLE_ERROR_CLASSES,
1333
1381
  };
@@ -24,7 +24,8 @@ import {
24
24
 
25
25
  const VALID_WRITE_AUTHORITIES = ['authoritative', 'proposed', 'review_only'];
26
26
  const VALID_RUNTIME_TYPES = ['manual', 'local_cli', 'api_proxy', 'mcp', 'remote_agent'];
27
- const VALID_API_PROXY_PROVIDERS = ['anthropic', 'openai', 'google'];
27
+ export const VALID_API_PROXY_PROVIDERS = ['anthropic', 'openai', 'google', 'ollama'];
28
+ const AUTH_OPTIONAL_PROVIDERS = ['ollama'];
28
29
  export const VALID_PROMPT_TRANSPORTS = ['argv', 'stdin', 'dispatch_bundle_only'];
29
30
  const VALID_MCP_TRANSPORTS = ['stdio', 'streamable_http'];
30
31
  const DEFAULT_PHASES = ['planning', 'implementation', 'qa'];
@@ -388,7 +389,9 @@ export function validateV4Config(data, projectRoot) {
388
389
  errors.push(`Runtime "${id}": api_proxy requires "model" (e.g. "claude-sonnet-4-6")`);
389
390
  }
390
391
  if (typeof rt.auth_env !== 'string' || !rt.auth_env.trim()) {
391
- errors.push(`Runtime "${id}": api_proxy requires "auth_env" (environment variable name for API key)`);
392
+ if (!AUTH_OPTIONAL_PROVIDERS.includes(rt.provider)) {
393
+ errors.push(`Runtime "${id}": api_proxy requires "auth_env" (environment variable name for API key)`);
394
+ }
392
395
  }
393
396
  if ('base_url' in rt) {
394
397
  if (typeof rt.base_url !== 'string' || !rt.base_url.trim()) {