@atmorouter/helper 0.2.2 → 0.2.4
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 +30 -11
- package/bin/atmorouter-helper.mjs +7 -4
- package/lib/helper.mjs +7 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
Configure coding agents for AtmoRouter, get IDE setup instructions, discover model IDs, and check a key without running inference. Changes are previewed first; automatic configuration merges existing settings and creates backups.
|
|
4
4
|
|
|
5
|
-
Requires Node.js 22 or newer. The
|
|
5
|
+
Requires Node.js 22 or newer. The package uses the `yaml` runtime dependency to safely parse and preserve YAML configuration. No repository access is required. Version 0.2.4 adds capacity cooldown and bounded retry guidance. The commands below use the versioned website archive.
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
|
-
npx
|
|
9
|
-
npx
|
|
10
|
-
npx
|
|
11
|
-
npx
|
|
12
|
-
npx
|
|
8
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz --help
|
|
9
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz targets
|
|
10
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz models --search claude
|
|
11
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz configure --target opencode --model YOUR_MODEL_ID
|
|
12
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz configure --target claude --model YOUR_MODEL_ID --write
|
|
13
13
|
```
|
|
14
14
|
|
|
15
|
-
The same version is available as `https://atmorouter.dev/downloads/atmorouter-helper-0.2.
|
|
15
|
+
The same version is available as `https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz`, with a `.sha256` checksum alongside it. Run it with `npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz --help`.
|
|
16
16
|
|
|
17
17
|
With no command, the interactive wizard searches live text models and previews the selected setup. `configure` is preview-only unless `--write` is supplied. Non-interactive file writes additionally require `--yes`. Tests use temporary `--config-dir` folders, never real agent settings.
|
|
18
18
|
|
|
@@ -36,13 +36,27 @@ Guided targets print the steps to perform in your installed client; they do not
|
|
|
36
36
|
|
|
37
37
|
Use `--shell powershell` for PowerShell snippets. `--config-dir PATH` explicitly selects a settings folder. `--offline` skips live model validation; it does not establish model availability or client compatibility. Preview output shows intended changes, not existing file contents.
|
|
38
38
|
|
|
39
|
+
## Budgets after setup
|
|
40
|
+
|
|
41
|
+
After an automatic setup, preview, manual IDE integration, or bridge launch, open [Budgets](https://atmorouter.dev/dashboard/budgets). Select the actual API key used by that client, then choose an all-model percentage rule for broad coverage. Illustrative examples only: 20% below official rates or 50% below official rates. Review the displayed rates and choose your own limit. A family or exact-model rule covers only that target. Account and key rules both apply; the strictest input and output limits win. The helper never saves or raises budget rules automatically.
|
|
42
|
+
|
|
43
|
+
Market prices and tiers vary. A “From” price is a snapshot of the cheapest advertised offer, not a guaranteed execution price. Setup and key checks do not prove current availability within your limits. The CLI sends these reminders to stderr so piping an environment snippet from stdout still produces valid shell commands. Generated configuration files contain client settings only.
|
|
44
|
+
|
|
45
|
+
## Waiting and retries in your harness
|
|
46
|
+
|
|
47
|
+
Routing can wait up to 30 seconds for capacity within your caps. This wait is fixed; generation time is additional. Set the client timeout to allow both routing and the expected generation duration. The local bridge defaults to a 120-second total timeout, including routing and generation; a caller using the bridge must allow for that total duration.
|
|
48
|
+
|
|
49
|
+
For HTTP 429 or temporary availability failures, honor `Retry-After` when present. Otherwise use bounded exponential backoff with jitter. Avoid blind retries and duplicate in-flight requests. After a timeout or interrupted stream, review the client outcome and usage before deciding to resend; a lost response does not establish that the request was unprocessed. The bridge does not automatically retry inference or raise price caps.
|
|
50
|
+
|
|
51
|
+
Correct invalid requests before retrying. For authentication errors, verify the key and account eligibility. For insufficient balance, review Billing. For `balance_temporarily_reserved`, allow existing requests to finish. For `price_limit_unavailable`, wait for capacity within your caps or deliberately review your budget. Adding funds does not resolve pricing availability.
|
|
52
|
+
|
|
39
53
|
## Credentials
|
|
40
54
|
|
|
41
55
|
The helper never saves or prints your API key. Set `ATMOROUTER_API_KEY` privately through your shell or secret manager. OpenCode reads `{env:ATMOROUTER_API_KEY}`; Oh My Pi reads the environment variable named by `apiKey`. Continue uses `${{ secrets.ATMOROUTER_API_KEY }}`; for the IDE, manually add the secret to `~/.continue/.env` without committing that file. For Claude Code, set `ANTHROPIC_AUTH_TOKEN` to the same key before launching Claude. Guided IDE targets require you to enter the key in the client's own credential UI.
|
|
42
56
|
|
|
43
57
|
```sh
|
|
44
58
|
export ANTHROPIC_AUTH_TOKEN="$ATMOROUTER_API_KEY"
|
|
45
|
-
npx
|
|
59
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz check-key
|
|
46
60
|
```
|
|
47
61
|
|
|
48
62
|
`GET /v1/models` is public and **cannot validate a key**. The optional explicit key check sends an empty JSON body to `/v1/chat/completions`. Only HTTP 400 with `model is required` proves the gateway accepted the credential before rejecting the body. No model is selected, no inference is forwarded, and nothing is billed. The check consumes a rate-limit slot. HTTP 401 rejects the key; 429 and all other responses are inconclusive. The key-check request has a 15-second timeout and refuse redirects. Catalog discovery has a 30-second timeout. No custom upstream origin can receive your key.
|
|
@@ -53,13 +67,13 @@ The public gateway implements Chat Completions and Anthropic Messages, not `/v1/
|
|
|
53
67
|
|
|
54
68
|
```sh
|
|
55
69
|
# Prints instructions only; does not start a server or write Codex settings.
|
|
56
|
-
npx
|
|
70
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz configure --target codex --model YOUR_MODEL_ID
|
|
57
71
|
|
|
58
72
|
# Starts a temporary local bridge and launches your installed Codex CLI.
|
|
59
|
-
npx
|
|
73
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz codex --model YOUR_MODEL_ID
|
|
60
74
|
|
|
61
75
|
# Or run the bridge separately and follow its printed client instructions.
|
|
62
|
-
npx
|
|
76
|
+
npx https://atmorouter.dev/downloads/atmorouter-helper-0.2.4.tgz bridge --port 43187
|
|
63
77
|
```
|
|
64
78
|
|
|
65
79
|
Set `ATMOROUTER_API_KEY` in the process environment before starting the bridge. It listens on loopback only, authenticates local requests, and forwards inference to the official AtmoRouter endpoint. The launcher preserves Codex's permission defaults. It does not weaken approvals or sandbox settings.
|
|
@@ -87,3 +101,8 @@ npm run test:package
|
|
|
87
101
|
The packed-install check uses an isolated npm cache and the public registry, without requiring account credentials. The archive contains only the package manifest, README, CLI, and three library modules; tests, scripts, local settings, and keys are excluded.
|
|
88
102
|
|
|
89
103
|
Client references: [Claude Code](https://code.claude.com/docs/en/llm-gateway), [OpenCode](https://opencode.ai/docs/providers/), [Oh My Pi](https://github.com/can1357/oh-my-pi), [Continue secrets](https://docs.continue.dev/customize/deep-dives/secrets), [Cursor keys](https://cursor.com/help/models-and-usage/api-keys), [VS Code language models](https://code.visualstudio.com/docs/agent-customization/language-models), [Cline](https://docs.cline.bot/provider-config/openai-compatible), [Roo Code](https://docs.roocode.com/providers/openai-compatible), and [Codex custom providers](https://developers.openai.com/codex/config-reference/).
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
For explicit capacity refusals before any output, use `Retry-After` or `error.retry_after_seconds`, including errors carried inside HTTP 200 SSE streams. Wait at least that delay plus a small random jitter, with at most three automatic retries. Without a delay hint, wait 2, 4, then 8 seconds plus jitter. Review Usage and tool outcomes before resending a request that produced output or has an uncertain outcome.
|
|
107
|
+
|
|
108
|
+
Capacity pauses use the model, API protocol and effective input/output price ceilings. Customer-specific ceilings are isolated by account. Three capacity refusals within one minute start a 15-second pause. One request probes recovery, with pauses increasing to 30 and then 60 seconds if capacity remains unavailable. The helper prints this guidance during setup. Retry behavior is controlled by the harness.
|
|
@@ -6,7 +6,7 @@ import fs from 'node:fs';
|
|
|
6
6
|
import path from 'node:path';
|
|
7
7
|
import {spawn} from 'node:child_process';
|
|
8
8
|
import {setDefaultResultOrder} from 'node:dns';
|
|
9
|
-
import {models,modelId,checkKey,planConfiguration,writeConfiguration,environment,INTEGRATIONS,guidedConfiguration} from '../lib/helper.mjs';
|
|
9
|
+
import {models,modelId,checkKey,planConfiguration,writeConfiguration,environment,INTEGRATIONS,guidedConfiguration,SETUP_GUIDANCE} from '../lib/helper.mjs';
|
|
10
10
|
import {startResponsesBridge} from '../lib/bridge.mjs';
|
|
11
11
|
|
|
12
12
|
// Prefer IPv4 when both families are advertised; IPv6-only DNS still works.
|
|
@@ -59,6 +59,7 @@ async function launchCodex(model,args){
|
|
|
59
59
|
const bridge=await startResponsesBridge({port:0});
|
|
60
60
|
try {
|
|
61
61
|
console.log('Starting Codex through a loopback-only bridge. Hosted web search is disabled for this run. Model requests are billed by AtmoRouter.');
|
|
62
|
+
console.error(SETUP_GUIDANCE);
|
|
62
63
|
const configuration=['-c','model_provider="atmorouter"','-c','model='+JSON.stringify(model),'-c',`model_providers.atmorouter={name="AtmoRouter",base_url="${bridge.url}",env_key="ATMOROUTER_API_KEY",wire_api="responses",requires_openai_auth=false,supports_websockets=false}`,'-c','web_search="disabled"'];
|
|
63
64
|
const code=await new Promise((resolve,reject)=>{
|
|
64
65
|
const child=spawn(executable.file,[...executable.args,...configuration,...args],{stdio:'inherit',env:process.env});
|
|
@@ -84,6 +85,7 @@ async function main(){
|
|
|
84
85
|
if(command==='bridge'){
|
|
85
86
|
if(!/^\d+$/.test(values.port)||Number(values.port)<1||Number(values.port)>65535)throw Error('Bridge port must be an integer from 1 to 65535.');
|
|
86
87
|
const bridge=await startResponsesBridge({port:Number(values.port)});
|
|
88
|
+
console.error(SETUP_GUIDANCE);
|
|
87
89
|
console.log('Local Responses bridge: '+bridge.url+'\nStateless text/tools only; normal AtmoRouter charges apply. Press Ctrl+C to stop.');
|
|
88
90
|
console.log('In your Codex configuration, set model_provider = "atmorouter" and your chosen model and web_search = "disabled" before provider tables. Then add:\n\n[model_providers.atmorouter]\nname = "AtmoRouter local bridge"\nbase_url = "'+bridge.url+'"\nenv_key = "ATMOROUTER_API_KEY"\nwire_api = "responses"\nrequires_openai_auth = false\nsupports_websockets = false\n\nSet ATMOROUTER_API_KEY privately in the terminal that starts Codex. Run configure --target codex --model YOUR_MODEL_ID for full instructions.');
|
|
89
91
|
await new Promise(resolve=>{process.once('SIGINT',resolve);process.once('SIGTERM',resolve);});await bridge.close();return;
|
|
@@ -118,12 +120,12 @@ async function main(){
|
|
|
118
120
|
if(integration.mode==='unavailable')throw Error(guidedConfiguration(target,model,{shell:values.shell}));
|
|
119
121
|
if(['guided','bridge'].includes(integration.mode)){
|
|
120
122
|
if(values.write)throw Error('This integration uses guided setup. No configuration files were changed.');
|
|
121
|
-
console.log(guidedConfiguration(target,model,{shell:values.shell}));return;
|
|
123
|
+
console.log(guidedConfiguration(target,model,{shell:values.shell}));console.error(SETUP_GUIDANCE);return;
|
|
122
124
|
}
|
|
123
125
|
if(!values.offline&&!rl){if(!(await models()).some(m=>m.id===model))throw Error('Model is not in the live text catalog. Use models to choose one.');}
|
|
124
126
|
if(['openai','anthropic'].includes(target)){
|
|
125
127
|
if(values.write)throw Error('Environment snippets are preview-only. Copy them into your shell.');
|
|
126
|
-
console.log(environment(target,model,values.shell));return;
|
|
128
|
+
console.log(environment(target,model,values.shell));console.error(SETUP_GUIDANCE);return;
|
|
127
129
|
}
|
|
128
130
|
const plan=await planConfiguration(target,model,{directory:values['config-dir']});
|
|
129
131
|
// Print only the intended patch. Existing settings may contain secrets.
|
|
@@ -136,8 +138,9 @@ async function main(){
|
|
|
136
138
|
if(values.yes)apply=true;
|
|
137
139
|
else {if(!process.stdin.isTTY)throw Error('Non-interactive writes require --write --yes. Nothing changed.');rl=createInterface({input:process.stdin,output:process.stdout});apply=/^y(es)?$/i.test((await rl.question('Apply these changes? [y/N] ')).trim());}
|
|
138
140
|
}
|
|
139
|
-
if(!apply){console.log('Preview only. No files changed.');return;}
|
|
141
|
+
if(!apply){console.log('Preview only. No files changed.');console.error(SETUP_GUIDANCE);return;}
|
|
140
142
|
const written=await writeConfiguration(plan);console.log('Saved: '+written.file);if(written.backup)console.log('Backup: '+written.backup);
|
|
143
|
+
console.error(SETUP_GUIDANCE);
|
|
141
144
|
} finally {rl?.close();}
|
|
142
145
|
}
|
|
143
146
|
main().catch(error=>{
|
package/lib/helper.mjs
CHANGED
|
@@ -6,6 +6,13 @@ import {planYamlConfiguration,assertSafeConfigurationPath} from './integrations.
|
|
|
6
6
|
export {INTEGRATIONS,guidedConfiguration} from './integrations.mjs';
|
|
7
7
|
|
|
8
8
|
export const ORIGIN = 'https://atmorouter.dev';
|
|
9
|
+
export const SETUP_GUIDANCE = `Before using your client: review Budgets at https://atmorouter.dev/dashboard/budgets.
|
|
10
|
+
Market prices and tiers vary. A "From" price is a snapshot of the cheapest advertised offer, not a guaranteed execution price.
|
|
11
|
+
Select the actual API key used by this client and an all-model percentage rule for broad coverage. Illustrative examples only: 20% below official rates, or 50% below official rates; choose your own limit after reviewing the rates. Account and key rules both apply; the strictest input and output limits win. A rule for one model does not cover other models. The helper never saves or raises budget rules automatically.
|
|
12
|
+
Routing can wait up to 30 seconds for capacity within your caps. This wait is fixed; generation time is additional. Allow both in your client timeout. The local bridge has a 120-second total timeout by default.
|
|
13
|
+
For 429 or temporary availability failures, honor Retry-After when present; otherwise use bounded exponential backoff with jitter. Do not retry blindly or duplicate an in-flight request. After a timeout or interrupted stream, review the client outcome and usage before deciding to resend. Never raise price caps automatically.
|
|
14
|
+
Capacity refusals can pause the affected model, protocol and effective price ceilings for 15, 30, then up to 60 seconds. Customer-specific limits are isolated by account. One eligible request tests recovery. For an explicit capacity rejection before output, honor Retry-After or error.retry_after_seconds, including errors inside HTTP 200 SSE streams. Wait at least that delay plus a small random jitter; allow at most three automatic retries. Without a delay hint, wait 2, 4, then 8 seconds plus jitter. If output arrived, tools ran, or the outcome is uncertain, review Usage and the client outcome before resending.
|
|
15
|
+
For invalid requests, correct the request; for authentication errors, check the key and account eligibility; for insufficient balance, review Billing. A price_limit_unavailable refusal needs capacity within your caps or a deliberate budget review; adding funds does not fix pricing availability. Client setup and key checks do not prove current availability within your limits.`;
|
|
9
16
|
const isRecord = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
10
17
|
export function modelId(value) {
|
|
11
18
|
if (typeof value!=='string' || value!==value.trim() || !/^[A-Za-z0-9][A-Za-z0-9._:/-]{0,200}$/.test(value)) throw Error('Choose a model ID from the catalog; shell characters and whitespace are not allowed.');
|
package/package.json
CHANGED