Skip to content

Commit 1295c32

Browse files
committed
Merge remote-tracking branch 'origin/main' into prepare-pr811-fixes
# Conflicts: # src/agent/agent-task-executor.js
2 parents 4a7323e + acffbbb commit 1295c32

34 files changed

Lines changed: 1903 additions & 138 deletions

CONTRIBUTING.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Thank you for your interest in contributing to Zeroshot! This guide covers every
2222
- **Node.js 18+** (check: `node --version`)
2323
- **npm** (bundled with Node)
2424
- **Docker** (optional, for isolation mode tests)
25-
- **Claude Code CLI** - `npm i -g @anthropic-ai/claude-code && claude auth login`
25+
- **AI provider** - Configure the bundled Gateway provider or install at least one supported provider
26+
CLI: Claude Code, Codex, Gemini, Opencode, Pi, Kiro, or Copilot (see
27+
[provider setup and installation instructions](./docs/providers.md))
2628
- **GitHub CLI** - Required for PR creation features ([install guide](https://cli.github.com/))
2729

2830
### Installation

docs/providers.md

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
Zeroshot supports two provider shapes:
44

55
- CLI-backed providers that shell out to a full agent CLI
6-
- One bundled `gateway` provider that wraps OpenAI-compatible model APIs with a
7-
Zeroshot-owned tool runner
6+
- One bundled `gateway` provider that wraps OpenAI-compatible or Anthropic-compatible
7+
model APIs with a Zeroshot-owned tool runner
88

99
## Supported Providers
1010

@@ -29,16 +29,17 @@ Zeroshot supports two provider shapes:
2929

3030
## Gateway Provider
3131

32-
Use `gateway` for OpenAI-compatible model endpoints such as OpenRouter,
33-
Ollama, vLLM, or self-hosted gateways. These stay model configs behind one
34-
provider engine; do not add them as standalone provider ids.
32+
Use `gateway` for OpenAI-compatible or Anthropic-compatible model endpoints.
33+
These stay model configs behind one provider engine; do not add them as
34+
standalone provider ids.
3535

3636
Required settings:
3737

3838
```json
3939
{
4040
"providerSettings": {
4141
"gateway": {
42+
"protocol": "openai",
4243
"baseUrl": "http://127.0.0.1:11434",
4344
"apiKey": "gateway-key",
4445
"model": "openrouter/meta-llama/test-model",
@@ -53,10 +54,49 @@ Required settings:
5354

5455
Notes:
5556

57+
- `protocol` defaults to `openai`; set it to `anthropic` for Messages API endpoints.
58+
- Anthropic-compatible configurations require a positive `maxTokens` value.
5659
- `toolPolicy` is required. There is no default file or shell access.
5760
- `headers` is optional for extra gateway-specific request headers.
5861
- `model` may be any non-empty provider-specific model id.
5962

63+
### MiniMax
64+
65+
The gateway model catalog includes `MiniMax-M3` and `MiniMax-M2.7`. Choose the
66+
region and protocol with the matching base URL:
67+
68+
| Region | Protocol | Base URL |
69+
| ------ | ----------- | ------------------------------------ |
70+
| Global | `openai` | `https://api.minimax.io/v1` |
71+
| Global | `anthropic` | `https://api.minimax.io/anthropic` |
72+
| China | `openai` | `https://api.minimaxi.com/v1` |
73+
| China | `anthropic` | `https://api.minimaxi.com/anthropic` |
74+
75+
Example Anthropic-compatible settings:
76+
77+
```json
78+
{
79+
"providerSettings": {
80+
"gateway": {
81+
"protocol": "anthropic",
82+
"baseUrl": "https://api.minimax.io/anthropic",
83+
"apiKey": "your-api-key",
84+
"model": "MiniMax-M3",
85+
"maxTokens": 8192,
86+
"toolPolicy": {
87+
"roots": ["/absolute/path/to/worktree"],
88+
"commands": ["node"]
89+
}
90+
}
91+
}
92+
}
93+
```
94+
95+
Pass the Anthropic base URL exactly as shown. The bundled client appends
96+
`/v1/messages` for each request. For OpenAI-compatible settings, use
97+
`"protocol": "openai"` and omit `maxTokens` unless the endpoint needs a custom
98+
limit.
99+
60100
## Model Levels
61101

62102
Zeroshot uses provider-agnostic levels:

scripts/assert-release-published.js

Lines changed: 99 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -179,28 +179,74 @@ function sleep(ms) {
179179
});
180180
}
181181

182+
function nextRetryDelay(attempt, attempts, delayMs, options) {
183+
if (attempt >= attempts) return null;
184+
if (options.deadline === undefined) return delayMs;
185+
186+
const now = options.now || Date.now;
187+
const remainingMs = options.deadline - now();
188+
if (remainingMs <= 0) return null;
189+
return Math.min(delayMs, remainingMs);
190+
}
191+
182192
async function waitForNpmLatest(name, expectedVersion, options = {}) {
183193
const attempts =
184194
options.attempts || Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
185195
const delayMs =
186196
options.delayMs || Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
197+
const wait = options.sleep || sleep;
187198

188199
let latest = null;
189200
for (let attempt = 1; attempt <= attempts; attempt += 1) {
190201
latest = npmLatest(name);
191202
if (latest === expectedVersion) return latest;
192203

193-
if (attempt < attempts) {
204+
const retryDelay = nextRetryDelay(attempt, attempts, delayMs, options);
205+
if (retryDelay !== null) {
194206
console.log(
195207
`npm latest for ${name} is ${latest}; waiting for ${expectedVersion} (${attempt}/${attempts})`
196208
);
197-
await sleep(delayMs);
209+
await wait(retryDelay);
210+
} else {
211+
break;
198212
}
199213
}
200214

201215
throw new Error(`expected npm latest for ${name} to be ${expectedVersion}, got ${latest}`);
202216
}
203217

218+
async function waitForPublishedArtifact(label, check, options = {}) {
219+
const attempts =
220+
options.attempts || Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
221+
const delayMs =
222+
options.delayMs || Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
223+
const wait = options.sleep || sleep;
224+
let lastError = null;
225+
let attemptsMade = 0;
226+
227+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
228+
attemptsMade = attempt;
229+
try {
230+
return await check();
231+
} catch (error) {
232+
lastError = error;
233+
const retryDelay = nextRetryDelay(attempt, attempts, delayMs, options);
234+
if (retryDelay !== null) {
235+
console.log(
236+
`${label} is not ready: ${error.message}; retrying (${attempt}/${attempts})`
237+
);
238+
await wait(retryDelay);
239+
} else {
240+
break;
241+
}
242+
}
243+
}
244+
245+
throw new Error(
246+
`${label} did not become ready after ${attemptsMade} attempts: ${lastError?.message || 'unknown error'}`
247+
);
248+
}
249+
204250
async function main() {
205251
const name = packageName();
206252
const headTags = tagsPointingAtHead();
@@ -218,30 +264,62 @@ async function main() {
218264

219265
console.log(`tags on HEAD: ${headTags.join(', ') || '(none)'}`);
220266
const expectedVersion = expectedTag.slice(1);
221-
const latest = await waitForNpmLatest(name, expectedVersion);
267+
const retryAttempts = Number(process.env.RELEASE_ASSERT_ATTEMPTS || DEFAULT_ATTEMPTS);
268+
const retryDelayMs = Number(process.env.RELEASE_ASSERT_DELAY_MS || DEFAULT_DELAY_MS);
269+
const retryOptions = {
270+
attempts: retryAttempts,
271+
delayMs: retryDelayMs,
272+
deadline: Date.now() + retryAttempts * retryDelayMs,
273+
};
274+
const latest = await waitForNpmLatest(name, expectedVersion, retryOptions);
222275

223276
console.log(`npm latest for ${name}: ${latest}`);
224277

225278
const expectedCommit = run('git', ['rev-parse', 'HEAD']);
226-
const metadata = npmReleaseMetadata(name, expectedVersion);
227-
if (metadata.version !== expectedVersion) {
228-
throw new Error(`npm metadata returned ${metadata.version}; expected ${expectedVersion}`);
229-
}
230-
if (metadata.gitHead !== expectedCommit) {
231-
throw new Error(`npm gitHead ${metadata.gitHead || '(missing)'} does not match HEAD`);
232-
}
279+
const metadata = await waitForPublishedArtifact(
280+
'npm release metadata',
281+
() => {
282+
const result = npmReleaseMetadata(name, expectedVersion);
283+
if (result.version !== expectedVersion) {
284+
throw new Error(`npm metadata returned ${result.version}; expected ${expectedVersion}`);
285+
}
286+
if (result.gitHead !== expectedCommit) {
287+
throw new Error(`npm gitHead ${result.gitHead || '(missing)'} does not match HEAD`);
288+
}
289+
if (!result['dist.attestations']?.url) {
290+
throw new Error('npm attestation URL is missing');
291+
}
292+
return result;
293+
},
294+
retryOptions
295+
);
233296

234-
const attestationUrl = metadata['dist.attestations']?.url;
235-
if (!attestationUrl) throw new Error('npm attestation URL is missing');
236-
const attestations = await httpsJson(attestationUrl);
237-
verifyProvenance(provenanceStatement(attestations), expectedCommit);
297+
await waitForPublishedArtifact(
298+
'npm provenance',
299+
async () => {
300+
const attestations = await httpsJson(metadata['dist.attestations'].url);
301+
verifyProvenance(provenanceStatement(attestations), expectedCommit);
302+
},
303+
retryOptions
304+
);
238305

239-
const release = githubRelease(expectedTag);
240-
if (release.tagName !== expectedTag) {
241-
throw new Error(`GitHub Release tag ${release.tagName} does not match ${expectedTag}`);
242-
}
243-
verifyCuratedNotes(expectedTag, release);
244-
verifyInstalledCli(name, expectedVersion);
306+
await waitForPublishedArtifact(
307+
'GitHub Release',
308+
() => {
309+
const release = githubRelease(expectedTag);
310+
if (release.tagName !== expectedTag) {
311+
throw new Error(`GitHub Release tag ${release.tagName} does not match ${expectedTag}`);
312+
}
313+
verifyCuratedNotes(expectedTag, release);
314+
},
315+
retryOptions
316+
);
317+
318+
await waitForPublishedArtifact(
319+
'installed CLI',
320+
() => verifyInstalledCli(name, expectedVersion),
321+
retryOptions
322+
);
245323

246324
console.log(`Release publication verified: ${name}@${latest}`);
247325
}
@@ -263,4 +341,5 @@ module.exports = {
263341
verifyInstalledCli,
264342
verifyProvenance,
265343
waitForNpmLatest,
344+
waitForPublishedArtifact,
266345
};

src/agent-cli-provider/adapters/claude.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
4040
'claude-opus-4-6': { rank: 3 },
4141
'claude-opus-4-7': { rank: 3 },
4242
'claude-opus-4-8': { rank: 3 },
43+
'claude-opus-5': { rank: 3 },
4344
fable: { rank: 3 },
4445
'claude-fable-5': { rank: 3 },
4546
'claude-mythos-5': { rank: 3 },

src/agent-cli-provider/adapters/codex.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,11 @@ const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
2929
'gpt-5.5': { rank: 3 },
3030
'gpt-5.6': { rank: 3 },
3131
'gpt-5.6-sol': { rank: 3 },
32+
'openai.gpt-5.6-sol': { rank: 3 },
3233
'gpt-5.6-terra': { rank: 2 },
34+
'openai.gpt-5.6-terra': { rank: 2 },
3335
'gpt-5.6-luna': { rank: 1 },
36+
'openai.gpt-5.6-luna': { rank: 1 },
3437
};
3538

3639
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {

src/agent-cli-provider/adapters/gateway.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@ import { classifyBaseProviderError, commandSpec, createParserState, envRedaction
1818
import { resolveGatewayConfiguration, validateGatewaySettings } from '../gateway-tools';
1919
import { getBoolean, getString, isRecord, tryParseJson } from '../json';
2020

21-
const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {};
21+
const MODEL_CATALOG: Readonly<Record<string, ModelCatalogEntry>> = {
22+
'MiniMax-M3': { rank: 3 },
23+
'MiniMax-M2.7': { rank: 2 },
24+
};
2225

2326
const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
2427
level1: { rank: 1, model: null },
@@ -27,10 +30,12 @@ const LEVEL_MAPPING: Readonly<Record<ModelLevel, LevelModelSpec>> = {
2730
};
2831

2932
export const gatewaySettingsDefaults: Readonly<Record<string, unknown>> = Object.freeze({
33+
protocol: 'openai',
3034
baseUrl: null,
3135
apiKey: null,
3236
headers: null,
3337
model: null,
38+
maxTokens: null,
3439
toolPolicy: null,
3540
});
3641

@@ -52,8 +57,10 @@ function buildCommand(context: string, options: BuildProviderCommandOptions = {}
5257
context,
5358
cwd,
5459
gateway: {
60+
protocol: gateway.protocol,
5561
baseUrl: gateway.baseUrl,
5662
model: gateway.model,
63+
...(gateway.maxTokens === undefined ? {} : { maxTokens: gateway.maxTokens }),
5764
toolPolicy: gateway.toolPolicy,
5865
},
5966
...(Object.keys(headerEnv.mapping).length === 0 ? {} : { gatewayHeaderEnv: headerEnv.mapping }),
@@ -160,7 +167,7 @@ function classifyError(error: unknown): ErrorClassification {
160167
/\bmust be a valid url\b/i,
161168
/\btoolpolicy\b/i,
162169
/\bnon-empty model identifier\b/i,
163-
/\bgateway\.(?:baseUrl|apiKey|model|toolPolicy)\b/i,
170+
/\bgateway\.(?:protocol|baseUrl|apiKey|model|maxTokens|toolPolicy)\b/i,
164171
]
165172
);
166173
}

0 commit comments

Comments
 (0)