@idcloudhost/ai-connect 0.1.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/LICENSE +21 -0
- package/README.md +287 -0
- package/dist/adapters/aider.d.ts +11 -0
- package/dist/adapters/aider.d.ts.map +1 -0
- package/dist/adapters/aider.js +69 -0
- package/dist/adapters/aider.js.map +1 -0
- package/dist/adapters/claude-code.d.ts +17 -0
- package/dist/adapters/claude-code.d.ts.map +1 -0
- package/dist/adapters/claude-code.js +73 -0
- package/dist/adapters/claude-code.js.map +1 -0
- package/dist/adapters/codex.d.ts +11 -0
- package/dist/adapters/codex.d.ts.map +1 -0
- package/dist/adapters/codex.js +71 -0
- package/dist/adapters/codex.js.map +1 -0
- package/dist/adapters/continue.d.ts +7 -0
- package/dist/adapters/continue.d.ts.map +1 -0
- package/dist/adapters/continue.js +79 -0
- package/dist/adapters/continue.js.map +1 -0
- package/dist/adapters/crush.d.ts +4 -0
- package/dist/adapters/crush.d.ts.map +1 -0
- package/dist/adapters/crush.js +72 -0
- package/dist/adapters/crush.js.map +1 -0
- package/dist/adapters/goose.d.ts +11 -0
- package/dist/adapters/goose.d.ts.map +1 -0
- package/dist/adapters/goose.js +68 -0
- package/dist/adapters/goose.js.map +1 -0
- package/dist/adapters/helpers.d.ts +33 -0
- package/dist/adapters/helpers.d.ts.map +1 -0
- package/dist/adapters/helpers.js +117 -0
- package/dist/adapters/helpers.js.map +1 -0
- package/dist/adapters/hermes.d.ts +11 -0
- package/dist/adapters/hermes.d.ts.map +1 -0
- package/dist/adapters/hermes.js +100 -0
- package/dist/adapters/hermes.js.map +1 -0
- package/dist/adapters/index.d.ts +20 -0
- package/dist/adapters/index.d.ts.map +1 -0
- package/dist/adapters/index.js +60 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/manual.d.ts +6 -0
- package/dist/adapters/manual.d.ts.map +1 -0
- package/dist/adapters/manual.js +87 -0
- package/dist/adapters/manual.js.map +1 -0
- package/dist/adapters/omp.d.ts +8 -0
- package/dist/adapters/omp.d.ts.map +1 -0
- package/dist/adapters/omp.js +64 -0
- package/dist/adapters/omp.js.map +1 -0
- package/dist/adapters/openclaw.d.ts +10 -0
- package/dist/adapters/openclaw.d.ts.map +1 -0
- package/dist/adapters/openclaw.js +107 -0
- package/dist/adapters/openclaw.js.map +1 -0
- package/dist/adapters/opencode.d.ts +11 -0
- package/dist/adapters/opencode.d.ts.map +1 -0
- package/dist/adapters/opencode.js +86 -0
- package/dist/adapters/opencode.js.map +1 -0
- package/dist/adapters/pi-family.d.ts +14 -0
- package/dist/adapters/pi-family.d.ts.map +1 -0
- package/dist/adapters/pi-family.js +27 -0
- package/dist/adapters/pi-family.js.map +1 -0
- package/dist/adapters/pi.d.ts +8 -0
- package/dist/adapters/pi.d.ts.map +1 -0
- package/dist/adapters/pi.js +56 -0
- package/dist/adapters/pi.js.map +1 -0
- package/dist/adapters/shell-env.d.ts +12 -0
- package/dist/adapters/shell-env.d.ts.map +1 -0
- package/dist/adapters/shell-env.js +81 -0
- package/dist/adapters/shell-env.js.map +1 -0
- package/dist/adapters/zed.d.ts +10 -0
- package/dist/adapters/zed.d.ts.map +1 -0
- package/dist/adapters/zed.js +91 -0
- package/dist/adapters/zed.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +207 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/connect.d.ts +3 -0
- package/dist/commands/connect.d.ts.map +1 -0
- package/dist/commands/connect.js +179 -0
- package/dist/commands/connect.js.map +1 -0
- package/dist/commands/context.d.ts +21 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +4 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +85 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/env.d.ts +7 -0
- package/dist/commands/env.d.ts.map +1 -0
- package/dist/commands/env.js +37 -0
- package/dist/commands/env.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +39 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/login.d.ts +7 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +130 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/models.d.ts +9 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +51 -0
- package/dist/commands/models.js.map +1 -0
- package/dist/commands/remove.d.ts +3 -0
- package/dist/commands/remove.d.ts.map +1 -0
- package/dist/commands/remove.js +43 -0
- package/dist/commands/remove.js.map +1 -0
- package/dist/commands/snippet.d.ts +7 -0
- package/dist/commands/snippet.d.ts.map +1 -0
- package/dist/commands/snippet.js +91 -0
- package/dist/commands/snippet.js.map +1 -0
- package/dist/core/detect.d.ts +12 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +59 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/discovery.d.ts +46 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +124 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/gateway.d.ts +59 -0
- package/dist/core/gateway.d.ts.map +1 -0
- package/dist/core/gateway.js +385 -0
- package/dist/core/gateway.js.map +1 -0
- package/dist/core/http.d.ts +18 -0
- package/dist/core/http.d.ts.map +1 -0
- package/dist/core/http.js +73 -0
- package/dist/core/http.js.map +1 -0
- package/dist/core/management.d.ts +47 -0
- package/dist/core/management.d.ts.map +1 -0
- package/dist/core/management.js +88 -0
- package/dist/core/management.js.map +1 -0
- package/dist/core/store.d.ts +25 -0
- package/dist/core/store.d.ts.map +1 -0
- package/dist/core/store.js +48 -0
- package/dist/core/store.js.map +1 -0
- package/dist/fsutil/edit.d.ts +18 -0
- package/dist/fsutil/edit.d.ts.map +1 -0
- package/dist/fsutil/edit.js +102 -0
- package/dist/fsutil/edit.js.map +1 -0
- package/dist/fsutil/formats.d.ts +32 -0
- package/dist/fsutil/formats.d.ts.map +1 -0
- package/dist/fsutil/formats.js +154 -0
- package/dist/fsutil/formats.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +134 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/ui.d.ts +20 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +69 -0
- package/dist/ui.js.map +1 -0
- package/package.json +75 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 PT Cloud Hosting Indonesia (IDCloudHost)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
# @idcloudhost/ai-connect
|
|
2
|
+
|
|
3
|
+
Point any AI agent at the IDCloudHost PaaS AI gateway in one command.
|
|
4
|
+
|
|
5
|
+
The gateway is OpenAI-compatible, so in principle every agent below already works
|
|
6
|
+
with it. In practice each one wants the endpoint spelled out in its own config
|
|
7
|
+
file, in its own format, under its own key names — five different config
|
|
8
|
+
languages across the tools people actually use. This does that part for you, and
|
|
9
|
+
then proves the result works instead of assuming it.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npx @idcloudhost/ai-connect login # save your gateway URL + key once
|
|
13
|
+
npx @idcloudhost/ai-connect connect # configure every agent it finds
|
|
14
|
+
npx @idcloudhost/ai-connect doctor # prove it works
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## What it supports
|
|
18
|
+
|
|
19
|
+
| Agent | Written to | Notes |
|
|
20
|
+
|---|---|---|
|
|
21
|
+
| [opencode](https://opencode.ai) | `~/.config/opencode/opencode.json` | via `@ai-sdk/openai-compatible` |
|
|
22
|
+
| [pi](https://github.com/badlogic/pi-mono) | `~/.pi/agent/models.json` | |
|
|
23
|
+
| [omp (oh-my-pi)](https://github.com/can1357/oh-my-pi) | `~/.omp/agent/models.yml` | |
|
|
24
|
+
| [Hermes Agent](https://github.com/NousResearch/hermes-agent) | `~/.hermes/config.yaml` | one active model at a time |
|
|
25
|
+
| [OpenClaw](https://docs.openclaw.ai) | `~/.openclaw/openclaw.json` | provider **and** `agents.defaults.models` allowlist |
|
|
26
|
+
| [Codex CLI](https://developers.openai.com/codex) | `~/.codex/config.toml` | key must come from the environment |
|
|
27
|
+
| [Claude Code](https://docs.claude.com/en/docs/claude-code) | `~/.claude/settings.json` | needs the Anthropic `/v1/messages` route |
|
|
28
|
+
| [Crush](https://github.com/charmbracelet/crush) | `~/.config/crush/crush.json` | |
|
|
29
|
+
| [Goose](https://block.github.io/goose) | `~/.config/goose/config.yaml` | key lives in your OS keyring |
|
|
30
|
+
| [Aider](https://aider.chat) | `~/.aider.conf.yml` | key deliberately kept out of the file |
|
|
31
|
+
| [Continue](https://continue.dev) | `~/.continue/config.yaml` | |
|
|
32
|
+
| [Zed](https://zed.dev) | `~/.config/zed/settings.json` | key is entered in the Agent panel |
|
|
33
|
+
| Shell / anything else | `~/.config/idch-ai/env.sh` | `OPENAI_BASE_URL` + `OPENAI_API_KEY` |
|
|
34
|
+
|
|
35
|
+
Cline (and its forks Roo Code and Kilo Code), Cursor, and the Gemini-CLI forks
|
|
36
|
+
like Qwen Code keep their settings somewhere a CLI has no business writing —
|
|
37
|
+
VS Code's encrypted global state, a GUI keychain. For those it prints the exact
|
|
38
|
+
values to paste, filled in with your real URL and models.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm i -g @idcloudhost/ai-connect # then: idch-ai …
|
|
44
|
+
# or just: npx @idcloudhost/ai-connect …
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Node 18.17 or newer. No native dependencies.
|
|
48
|
+
|
|
49
|
+
## Getting a key
|
|
50
|
+
|
|
51
|
+
An organization admin enables AI and creates a virtual key in the IDCloudHost
|
|
52
|
+
dashboard. The secret is shown exactly once, so save it when it appears.
|
|
53
|
+
|
|
54
|
+
`idch-ai login` can do the lookup for you if you have a dashboard token — it
|
|
55
|
+
reads the gateway URL from the dashboard and can mint a key if you are an admin.
|
|
56
|
+
Otherwise paste the URL and key you already have.
|
|
57
|
+
|
|
58
|
+
The gateway URL is **not** hard-coded in this package. It is per-environment and
|
|
59
|
+
published by the dashboard at runtime, so the tool discovers it rather than
|
|
60
|
+
guessing.
|
|
61
|
+
|
|
62
|
+
## Commands
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
idch-ai connect [agents…] Write provider config for one, several, or all detected agents
|
|
66
|
+
idch-ai list Show supported agents and which are installed here
|
|
67
|
+
idch-ai models List the models this key may use, with context sizes and pricing
|
|
68
|
+
idch-ai doctor Probe the live gateway and check every agent config
|
|
69
|
+
idch-ai remove [agents…] Strip the provider block back out
|
|
70
|
+
idch-ai login / logout Manage the saved profile
|
|
71
|
+
idch-ai env [--shell fish|powershell|dotenv]
|
|
72
|
+
idch-ai snippet [curl|node|python|vercel-ai|langchain]
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Run `idch-ai connect` with no arguments and it detects what you have installed
|
|
76
|
+
and asks which to configure. Name them to skip the prompt:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
idch-ai connect opencode
|
|
80
|
+
idch-ai connect pi,omp,hermes
|
|
81
|
+
idch-ai connect all --model deepseek-chat
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## How it treats your config files
|
|
85
|
+
|
|
86
|
+
These are files you hand-edit, so it behaves like a careful colleague rather
|
|
87
|
+
than a code generator:
|
|
88
|
+
|
|
89
|
+
- **Nothing is clobbered.** Only the provider block keyed by `--provider-id`
|
|
90
|
+
(default `idcloudhost`) is touched. Your themes, keybindings, other providers
|
|
91
|
+
and unrelated settings are left exactly as they were.
|
|
92
|
+
- **Comments survive.** YAML and JSONC files are edited in place, not
|
|
93
|
+
reserialised, so `# do not lose this` is still there afterwards.
|
|
94
|
+
- **Backups, always.** Overwriting an existing file first writes
|
|
95
|
+
`<file>.idch-bak-<timestamp>` beside it.
|
|
96
|
+
- **Atomic writes.** Temp file plus rename, so an interrupted run can never
|
|
97
|
+
leave you with half a config.
|
|
98
|
+
- **Idempotent.** Running it twice changes nothing the second time — no backup
|
|
99
|
+
churn.
|
|
100
|
+
- **Reversible.** `idch-ai remove` takes the block back out and prunes the empty
|
|
101
|
+
containers it leaves behind.
|
|
102
|
+
|
|
103
|
+
Preview anything before it happens:
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
idch-ai connect openclaw --dry-run --print
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Where the key ends up
|
|
110
|
+
|
|
111
|
+
Some agents can only read a key from the environment (Codex, Goose, Zed), some
|
|
112
|
+
prefer a file, most accept either. Choose with `--secret`:
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
idch-ai connect opencode --secret env # writes {env:IDCH_AI_API_KEY}
|
|
116
|
+
idch-ai connect opencode --secret inline # writes the key into the file
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
Adapters that cannot honour your choice fall back to what the agent supports and
|
|
120
|
+
say so in the output. The saved profile at `~/.config/idch-ai/config.json` is
|
|
121
|
+
written `chmod 600`.
|
|
122
|
+
|
|
123
|
+
`idch-ai remove` strips config blocks; `idch-ai logout` forgets the profile. Do
|
|
124
|
+
both if you are rotating a key.
|
|
125
|
+
|
|
126
|
+
## doctor: measured, not assumed
|
|
127
|
+
|
|
128
|
+
The gateway is a LiteLLM proxy in front of whatever upstream models your
|
|
129
|
+
environment has configured. Which OpenAI features work is therefore a property
|
|
130
|
+
of your deployment and the model you picked — not something a compatibility
|
|
131
|
+
table can tell you. `doctor` makes real calls and reports what came back.
|
|
132
|
+
|
|
133
|
+
Illustrative output — the exact statuses and wording are your gateway's, not
|
|
134
|
+
ours:
|
|
135
|
+
|
|
136
|
+
```
|
|
137
|
+
Gateway capabilities
|
|
138
|
+
CHECK RESULT DETAIL
|
|
139
|
+
GET /models yes 7 models visible to this key
|
|
140
|
+
POST /chat/completions yes replied "ok"
|
|
141
|
+
Server-sent event streaming (stream: true) yes 14 chunks
|
|
142
|
+
Tool / function calling yes model returned a tool_call
|
|
143
|
+
response_format: json_object yes accepted
|
|
144
|
+
response_format: json_schema (structured output) no HTTP 4xx: <whatever your gateway said>
|
|
145
|
+
Image input (multimodal content parts) yes accepted
|
|
146
|
+
POST /embeddings yes 1 vector(s)
|
|
147
|
+
POST /responses (OpenAI Responses API) no HTTP 404: Not Found
|
|
148
|
+
POST /messages (Anthropic wire format) yes accepted
|
|
149
|
+
Rate limit / quota headers yes x-ratelimit-remaining-requests
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
A `no` means that gateway or that model does not support it. It is information,
|
|
153
|
+
not a failure of this tool. Claude Code in particular depends on the Anthropic
|
|
154
|
+
`/messages` row being `yes` — check before relying on it.
|
|
155
|
+
|
|
156
|
+
Classification is deliberately coarse: a `2xx` is a `yes`, anything else is a
|
|
157
|
+
`no`, and the gateway's own status and message are passed through verbatim in the
|
|
158
|
+
detail column. Nothing keys on matching a particular error string, because the
|
|
159
|
+
wording a gateway uses for an unsupported feature is not something this package
|
|
160
|
+
can know in advance — you read what your gateway actually said.
|
|
161
|
+
|
|
162
|
+
### What is known about `ai.paas.id` without a key
|
|
163
|
+
|
|
164
|
+
Verified 2026-08-30 by unauthenticated probing, which distinguishes a registered
|
|
165
|
+
route (LiteLLM's `auth_error` envelope) from an unregistered one (FastAPI's
|
|
166
|
+
`{"detail":"Not Found"}`):
|
|
167
|
+
|
|
168
|
+
- Registered under `/v1`: `messages`, `chat/completions`, `embeddings`,
|
|
169
|
+
`responses`, `completions`, `models`, `model/info`.
|
|
170
|
+
- The non-`/v1` mounts are **not** symmetric. `/chat/completions` and
|
|
171
|
+
`/embeddings` answer at the root too, `/models` answers `GET` only (405 to a
|
|
172
|
+
POST), and `/messages` is **`/v1` only** — it 404s at the root.
|
|
173
|
+
|
|
174
|
+
That last point is why the Claude Code adapter sets `ANTHROPIC_BASE_URL` to the
|
|
175
|
+
origin rather than the `/v1` base: Claude Code appends `/v1/messages` itself.
|
|
176
|
+
|
|
177
|
+
This tells you the routes are *registered*, not that any particular model behind
|
|
178
|
+
them answers. LiteLLM mounts `/v1/messages` regardless of what the upstream
|
|
179
|
+
supports. The functional question is what `doctor` answers, with your key and
|
|
180
|
+
your model.
|
|
181
|
+
|
|
182
|
+
### The base-URL probe checks its own assumption
|
|
183
|
+
|
|
184
|
+
Finding the right base URL means asking `/models` at a few candidate paths and
|
|
185
|
+
reading the status code. A `401` there normally means "the route exists, your key
|
|
186
|
+
is the problem" — but that inference only holds if the host returns `404` for
|
|
187
|
+
paths that *don't* exist. Put a proxy, WAF or login page in front that answers
|
|
188
|
+
before routing, and every path looks identical; the inference silently becomes
|
|
189
|
+
worthless and the probe would pick whichever candidate it tried first.
|
|
190
|
+
|
|
191
|
+
So it doesn't assume. Whenever a candidate answers with something other than
|
|
192
|
+
`200` or `404`, the probe issues one unauthenticated request to a path that
|
|
193
|
+
cannot exist (`/__idch-probe-<random>`; the random segment stops a cache or WAF
|
|
194
|
+
from holding a stored answer). If that returns `404`, the discrimination is live
|
|
195
|
+
and the verdict stands. If it returns anything uniform, the tool refuses to
|
|
196
|
+
guess:
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
✖ could not verify https://gateway.example: this host answers the same way for
|
|
200
|
+
paths that do not exist, so an auth error proves nothing about whether the
|
|
201
|
+
OpenAI routes are there. Something is likely replying before routing (a proxy,
|
|
202
|
+
WAF, or login page).
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
A `200` settles things outright, so the extra request costs nothing on the happy
|
|
206
|
+
path with a valid key.
|
|
207
|
+
|
|
208
|
+
## Programmatic use
|
|
209
|
+
|
|
210
|
+
Every command is available as a library, for devcontainer bootstraps and
|
|
211
|
+
internal installers:
|
|
212
|
+
|
|
213
|
+
```ts
|
|
214
|
+
import { resolveSource, buildTarget, getAdapter } from '@idcloudhost/ai-connect';
|
|
215
|
+
|
|
216
|
+
const source = await resolveSource({ baseUrl: process.env.IDCH_AI_BASE_URL, apiKey: key });
|
|
217
|
+
const target = await buildTarget(source, {});
|
|
218
|
+
|
|
219
|
+
await getAdapter('opencode')!.apply({
|
|
220
|
+
target,
|
|
221
|
+
dryRun: false,
|
|
222
|
+
secretMode: 'inline',
|
|
223
|
+
scope: 'user',
|
|
224
|
+
cwd: process.cwd(),
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
`probeCapabilities`, `listModels`, `resolveApiBase` and the `ManagementClient`
|
|
229
|
+
are exported too.
|
|
230
|
+
|
|
231
|
+
## Configuration reference
|
|
232
|
+
|
|
233
|
+
| Flag | Environment | Meaning |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `--base-url` | `IDCH_AI_BASE_URL` | Gateway base URL |
|
|
236
|
+
| `--api-key` | `IDCH_AI_API_KEY` | Virtual key |
|
|
237
|
+
| `--profile` | | Named saved profile |
|
|
238
|
+
| `--dashboard-url`, `--token`, `--org` | | Discover the gateway through the dashboard API |
|
|
239
|
+
| `--model` | | Model to make the default |
|
|
240
|
+
| `--only a,b` | | Register only these models |
|
|
241
|
+
| `--provider-id` | | Key the block is written under (default `idcloudhost`) |
|
|
242
|
+
| `--secret inline\|env` | | Where the key goes |
|
|
243
|
+
| `--scope user\|project` | | Home directory or the current project |
|
|
244
|
+
| `--config-path` | | Override the target file (single agent only) |
|
|
245
|
+
| `--dry-run`, `--print` | | Preview, with a diff |
|
|
246
|
+
| `--no-verify`, `--no-save` | | Skip the live test call / profile save |
|
|
247
|
+
| `--json` | | Machine-readable output |
|
|
248
|
+
| `--yes` | | Never prompt |
|
|
249
|
+
|
|
250
|
+
Values resolve in this order: flag, environment, saved profile, dashboard
|
|
251
|
+
lookup, interactive prompt.
|
|
252
|
+
|
|
253
|
+
## Adding an agent
|
|
254
|
+
|
|
255
|
+
An adapter is one file in `src/adapters/` implementing `detect`, `configPath`,
|
|
256
|
+
`apply` and `remove`, registered in `src/adapters/index.ts`. The integration
|
|
257
|
+
suite automatically covers every registered adapter: it checks the written
|
|
258
|
+
config points at the gateway, that a second run is a no-op, and that `remove`
|
|
259
|
+
leaves no trace.
|
|
260
|
+
|
|
261
|
+
```bash
|
|
262
|
+
npm install
|
|
263
|
+
npm run build
|
|
264
|
+
npm test
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
Tests run against a mock OpenAI-compatible server and a temporary `HOME`, so
|
|
268
|
+
they never touch your own agent configs.
|
|
269
|
+
|
|
270
|
+
## Known limits
|
|
271
|
+
|
|
272
|
+
- The gateway URL differs per environment. Always discover it; never hard-code it.
|
|
273
|
+
- Route detection relies on the gateway answering `404` for unknown paths. That
|
|
274
|
+
is checked on every run rather than assumed — see above — but if the check
|
|
275
|
+
fails you get an error, not a guess.
|
|
276
|
+
- Model ids are passed through verbatim. There is no provider prefix to add, and
|
|
277
|
+
the id you send must be one your key is allowed to use — the allowlist is set
|
|
278
|
+
per team and per key by an organization admin.
|
|
279
|
+
- Deleting a config block can take an adjacent comment with it if the comment was
|
|
280
|
+
attached to the key being removed. The backup path is printed for that reason.
|
|
281
|
+
- A route existing is not the same as a model serving it. Claude Code, the
|
|
282
|
+
Responses API and embeddings all need an upstream that actually answers, not
|
|
283
|
+
just a mounted path. `doctor` tells you which you have.
|
|
284
|
+
|
|
285
|
+
## License
|
|
286
|
+
|
|
287
|
+
MIT
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Adapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Aider routes through LiteLLM internally, so an OpenAI-compatible gateway is
|
|
4
|
+
* addressed as `openai/<model>` with `openai-api-base` repointed.
|
|
5
|
+
*
|
|
6
|
+
* The key is deliberately NOT written to the config file: aider's own docs push
|
|
7
|
+
* people to `.env` / the environment, and `~/.aider.conf.yml` is a file users
|
|
8
|
+
* commonly commit into dotfile repos.
|
|
9
|
+
*/
|
|
10
|
+
export declare const aiderAdapter: Adapter;
|
|
11
|
+
//# sourceMappingURL=aider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aider.d.ts","sourceRoot":"","sources":["../../src/adapters/aider.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAA6B,MAAM,aAAa,CAAC;AAUtE;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,OAqD1B,CAAC"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { readIfExists, shortenHome, writeFileSafely } from '../fsutil/edit.js';
|
|
3
|
+
import { editYaml, editYamlPruned, parseYaml } from '../fsutil/formats.js';
|
|
4
|
+
import { detectByBinOrConfig } from '../core/detect.js';
|
|
5
|
+
import { home, result } from './helpers.js';
|
|
6
|
+
function userConfig() {
|
|
7
|
+
return home('.aider.conf.yml');
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Aider routes through LiteLLM internally, so an OpenAI-compatible gateway is
|
|
11
|
+
* addressed as `openai/<model>` with `openai-api-base` repointed.
|
|
12
|
+
*
|
|
13
|
+
* The key is deliberately NOT written to the config file: aider's own docs push
|
|
14
|
+
* people to `.env` / the environment, and `~/.aider.conf.yml` is a file users
|
|
15
|
+
* commonly commit into dotfile repos.
|
|
16
|
+
*/
|
|
17
|
+
export const aiderAdapter = {
|
|
18
|
+
id: 'aider',
|
|
19
|
+
name: 'Aider',
|
|
20
|
+
summary: 'Pair-programming CLI. Config in ~/.aider.conf.yml, key from the environment.',
|
|
21
|
+
docsUrl: 'https://aider.chat/docs/config/aider_conf.html',
|
|
22
|
+
bins: ['aider'],
|
|
23
|
+
mode: 'auto',
|
|
24
|
+
supportsProjectScope: true,
|
|
25
|
+
secretModes: ['env'],
|
|
26
|
+
async detect() {
|
|
27
|
+
return detectByBinOrConfig(['aider'], [userConfig()]);
|
|
28
|
+
},
|
|
29
|
+
configPath({ scope, cwd, configPathOverride }) {
|
|
30
|
+
if (configPathOverride)
|
|
31
|
+
return configPathOverride;
|
|
32
|
+
return scope === 'project' ? path.join(cwd, '.aider.conf.yml') : userConfig();
|
|
33
|
+
},
|
|
34
|
+
async apply(opts) {
|
|
35
|
+
const file = this.configPath(opts);
|
|
36
|
+
const before = await readIfExists(file);
|
|
37
|
+
const { target } = opts;
|
|
38
|
+
const after = editYaml(before, (doc) => {
|
|
39
|
+
doc.setIn(['openai-api-base'], target.apiBase);
|
|
40
|
+
if (target.defaultModel)
|
|
41
|
+
doc.setIn(['model'], `openai/${target.defaultModel}`);
|
|
42
|
+
});
|
|
43
|
+
const change = await writeFileSafely(file, after, { dryRun: opts.dryRun });
|
|
44
|
+
return result(this.id, [change], {
|
|
45
|
+
envVars: { OPENAI_API_KEY: target.apiKey },
|
|
46
|
+
notes: [
|
|
47
|
+
'Export OPENAI_API_KEY before running aider — the key is kept out of the config file on purpose.',
|
|
48
|
+
'Models are addressed as openai/<model-id>; aider resolves them through LiteLLM.',
|
|
49
|
+
'Unknown model ids make aider warn about missing metadata. That is cosmetic; add .aider.model.metadata.json to silence it.',
|
|
50
|
+
],
|
|
51
|
+
tryCommand: target.defaultModel ? `aider --model openai/${target.defaultModel}` : 'aider',
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
async remove(opts) {
|
|
55
|
+
const file = this.configPath(opts);
|
|
56
|
+
const before = await readIfExists(file);
|
|
57
|
+
if (before === null)
|
|
58
|
+
return result(this.id, []);
|
|
59
|
+
const model = parseYaml(before).model;
|
|
60
|
+
const after = editYamlPruned(before, (doc) => {
|
|
61
|
+
doc.deleteIn(['openai-api-base']);
|
|
62
|
+
if (typeof model === 'string' && model.startsWith('openai/'))
|
|
63
|
+
doc.deleteIn(['model']);
|
|
64
|
+
});
|
|
65
|
+
const change = await writeFileSafely(file, after, { dryRun: opts.dryRun });
|
|
66
|
+
return result(this.id, [change], { notes: [`Cleared the gateway keys from ${shortenHome(file)}.`] });
|
|
67
|
+
},
|
|
68
|
+
};
|
|
69
|
+
//# sourceMappingURL=aider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"aider.js","sourceRoot":"","sources":["../../src/adapters/aider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { Adapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Claude Code speaks the Anthropic wire format, not OpenAI's. It works against
|
|
4
|
+
* our gateway only through LiteLLM's Anthropic-compatible `/v1/messages` route.
|
|
5
|
+
*
|
|
6
|
+
* `ANTHROPIC_BASE_URL` must be the ORIGIN, because Claude Code appends
|
|
7
|
+
* `/v1/messages` itself. This is not a guess: on ai.paas.id the Anthropic route
|
|
8
|
+
* is mounted under `/v1` only — `/v1/messages` exists while `/messages` returns
|
|
9
|
+
* 404, unlike `/chat/completions` and `/embeddings` which answer at both. Setting
|
|
10
|
+
* the base to the `/v1` URL would request `/v1/v1/messages` and 404.
|
|
11
|
+
*
|
|
12
|
+
* That the route is registered does not mean the model behind it serves Anthropic
|
|
13
|
+
* requests — LiteLLM mounts /v1/messages regardless of upstream config. Only an
|
|
14
|
+
* authenticated call settles it, which is what `idch-ai doctor` makes.
|
|
15
|
+
*/
|
|
16
|
+
export declare const claudeCodeAdapter: Adapter;
|
|
17
|
+
//# sourceMappingURL=claude-code.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.d.ts","sourceRoot":"","sources":["../../src/adapters/claude-code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA6B,MAAM,aAAa,CAAC;AAUtE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,iBAAiB,EAAE,OAuD/B,CAAC"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { readIfExists, shortenHome, writeFileSafely } from '../fsutil/edit.js';
|
|
2
|
+
import { parseJson, stringifyJson } from '../fsutil/formats.js';
|
|
3
|
+
import { detectByBinOrConfig } from '../core/detect.js';
|
|
4
|
+
import { deleteIn, home, result, setIn } from './helpers.js';
|
|
5
|
+
function userConfig() {
|
|
6
|
+
return home('.claude', 'settings.json');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Claude Code speaks the Anthropic wire format, not OpenAI's. It works against
|
|
10
|
+
* our gateway only through LiteLLM's Anthropic-compatible `/v1/messages` route.
|
|
11
|
+
*
|
|
12
|
+
* `ANTHROPIC_BASE_URL` must be the ORIGIN, because Claude Code appends
|
|
13
|
+
* `/v1/messages` itself. This is not a guess: on ai.paas.id the Anthropic route
|
|
14
|
+
* is mounted under `/v1` only — `/v1/messages` exists while `/messages` returns
|
|
15
|
+
* 404, unlike `/chat/completions` and `/embeddings` which answer at both. Setting
|
|
16
|
+
* the base to the `/v1` URL would request `/v1/v1/messages` and 404.
|
|
17
|
+
*
|
|
18
|
+
* That the route is registered does not mean the model behind it serves Anthropic
|
|
19
|
+
* requests — LiteLLM mounts /v1/messages regardless of upstream config. Only an
|
|
20
|
+
* authenticated call settles it, which is what `idch-ai doctor` makes.
|
|
21
|
+
*/
|
|
22
|
+
export const claudeCodeAdapter = {
|
|
23
|
+
id: 'claude-code',
|
|
24
|
+
name: 'Claude Code',
|
|
25
|
+
summary: "Anthropic's terminal agent. Uses the gateway's Anthropic-compatible /v1/messages route.",
|
|
26
|
+
docsUrl: 'https://docs.claude.com/en/docs/claude-code/settings',
|
|
27
|
+
bins: ['claude'],
|
|
28
|
+
mode: 'auto',
|
|
29
|
+
secretModes: ['inline', 'env'],
|
|
30
|
+
async detect() {
|
|
31
|
+
return detectByBinOrConfig(['claude'], [userConfig(), home('.claude')]);
|
|
32
|
+
},
|
|
33
|
+
configPath({ configPathOverride }) {
|
|
34
|
+
return configPathOverride ?? userConfig();
|
|
35
|
+
},
|
|
36
|
+
async apply(opts) {
|
|
37
|
+
const file = this.configPath(opts);
|
|
38
|
+
const before = await readIfExists(file);
|
|
39
|
+
const doc = parseJson(before);
|
|
40
|
+
const { target } = opts;
|
|
41
|
+
const env = {
|
|
42
|
+
ANTHROPIC_BASE_URL: target.origin,
|
|
43
|
+
ANTHROPIC_AUTH_TOKEN: target.apiKey,
|
|
44
|
+
};
|
|
45
|
+
if (target.defaultModel)
|
|
46
|
+
env.ANTHROPIC_MODEL = target.defaultModel;
|
|
47
|
+
const existing = doc.env;
|
|
48
|
+
setIn(doc, ['env'], { ...(existing && typeof existing === 'object' ? existing : {}), ...env });
|
|
49
|
+
const change = await writeFileSafely(file, stringifyJson(doc, before), { dryRun: opts.dryRun });
|
|
50
|
+
return result(this.id, [change], {
|
|
51
|
+
notes: [
|
|
52
|
+
'Claude Code needs a model that answers on the Anthropic /v1/messages route. The route existing is not enough — confirm with: idch-ai doctor',
|
|
53
|
+
`ANTHROPIC_BASE_URL is set to the origin (${target.origin}) on purpose — Claude Code appends /v1/messages itself, and that route is mounted under /v1 only.`,
|
|
54
|
+
'This writes a live token into ~/.claude/settings.json. Use `idch-ai env --shell` instead if you would rather keep it out of a file.',
|
|
55
|
+
'Run `claude` in a new shell so the settings are picked up.',
|
|
56
|
+
],
|
|
57
|
+
tryCommand: 'claude',
|
|
58
|
+
});
|
|
59
|
+
},
|
|
60
|
+
async remove(opts) {
|
|
61
|
+
const file = this.configPath(opts);
|
|
62
|
+
const before = await readIfExists(file);
|
|
63
|
+
if (before === null)
|
|
64
|
+
return result(this.id, []);
|
|
65
|
+
const doc = parseJson(before);
|
|
66
|
+
for (const key of ['ANTHROPIC_BASE_URL', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_MODEL']) {
|
|
67
|
+
deleteIn(doc, ['env', key]);
|
|
68
|
+
}
|
|
69
|
+
const change = await writeFileSafely(file, stringifyJson(doc, before), { dryRun: opts.dryRun });
|
|
70
|
+
return result(this.id, [change], { notes: [`Cleared the gateway env vars from ${shortenHome(file)}.`] });
|
|
71
|
+
},
|
|
72
|
+
};
|
|
73
|
+
//# sourceMappingURL=claude-code.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"claude-code.js","sourceRoot":"","sources":["../../src/adapters/claude-code.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Adapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* OpenAI Codex CLI, ~/.codex/config.toml.
|
|
4
|
+
*
|
|
5
|
+
* Codex reads the key from an environment variable named by `env_key` and has no
|
|
6
|
+
* inline-secret form, so this adapter is env-only by design.
|
|
7
|
+
* `wire_api = "chat"` selects /chat/completions; "responses" selects /responses,
|
|
8
|
+
* which our gateway only serves if the upstream supports it — check with `doctor`.
|
|
9
|
+
*/
|
|
10
|
+
export declare const codexAdapter: Adapter;
|
|
11
|
+
//# sourceMappingURL=codex.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.d.ts","sourceRoot":"","sources":["../../src/adapters/codex.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,OAAO,EAA6B,MAAM,aAAa,CAAC;AAUtE;;;;;;;GAOG;AACH,eAAO,MAAM,YAAY,EAAE,OAwD1B,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import { readIfExists, shortenHome, writeFileSafely } from '../fsutil/edit.js';
|
|
4
|
+
import { parseToml, stringifyToml } from '../fsutil/formats.js';
|
|
5
|
+
import { detectByBinOrConfig } from '../core/detect.js';
|
|
6
|
+
import { KEY_ENV, deleteIn, result, setIn } from './helpers.js';
|
|
7
|
+
function userConfig() {
|
|
8
|
+
return path.join(process.env.CODEX_HOME ?? path.join(os.homedir(), '.codex'), 'config.toml');
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* OpenAI Codex CLI, ~/.codex/config.toml.
|
|
12
|
+
*
|
|
13
|
+
* Codex reads the key from an environment variable named by `env_key` and has no
|
|
14
|
+
* inline-secret form, so this adapter is env-only by design.
|
|
15
|
+
* `wire_api = "chat"` selects /chat/completions; "responses" selects /responses,
|
|
16
|
+
* which our gateway only serves if the upstream supports it — check with `doctor`.
|
|
17
|
+
*/
|
|
18
|
+
export const codexAdapter = {
|
|
19
|
+
id: 'codex',
|
|
20
|
+
name: 'Codex CLI',
|
|
21
|
+
summary: "OpenAI's terminal coding agent. Providers live in ~/.codex/config.toml.",
|
|
22
|
+
docsUrl: 'https://developers.openai.com/codex/config-reference',
|
|
23
|
+
bins: ['codex'],
|
|
24
|
+
mode: 'auto',
|
|
25
|
+
secretModes: ['env'],
|
|
26
|
+
async detect() {
|
|
27
|
+
return detectByBinOrConfig(['codex'], [userConfig()]);
|
|
28
|
+
},
|
|
29
|
+
configPath({ configPathOverride }) {
|
|
30
|
+
return configPathOverride ?? userConfig();
|
|
31
|
+
},
|
|
32
|
+
async apply(opts) {
|
|
33
|
+
const file = this.configPath(opts);
|
|
34
|
+
const before = await readIfExists(file);
|
|
35
|
+
const doc = parseToml(before);
|
|
36
|
+
const { target } = opts;
|
|
37
|
+
setIn(doc, ['model_providers', target.providerId], {
|
|
38
|
+
name: target.providerLabel,
|
|
39
|
+
base_url: target.apiBase,
|
|
40
|
+
env_key: KEY_ENV,
|
|
41
|
+
wire_api: 'chat',
|
|
42
|
+
});
|
|
43
|
+
doc.model_provider = target.providerId;
|
|
44
|
+
if (target.defaultModel)
|
|
45
|
+
doc.model = target.defaultModel;
|
|
46
|
+
const change = await writeFileSafely(file, stringifyToml(doc), { dryRun: opts.dryRun });
|
|
47
|
+
return result(this.id, [change], {
|
|
48
|
+
// Codex has no inline-key form; the env var is mandatory regardless of --secret.
|
|
49
|
+
envVars: { [KEY_ENV]: target.apiKey },
|
|
50
|
+
notes: [
|
|
51
|
+
`Codex reads the key from $${KEY_ENV} — export it before starting codex.`,
|
|
52
|
+
'Switch providers per run with: codex --config model_provider=<id>',
|
|
53
|
+
`If the model rejects requests, try wire_api = "responses" in ${shortenHome(file)} — the gateway only supports it when the upstream does.`,
|
|
54
|
+
],
|
|
55
|
+
tryCommand: target.defaultModel ? `codex --model ${target.defaultModel}` : 'codex',
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
async remove(opts) {
|
|
59
|
+
const file = this.configPath(opts);
|
|
60
|
+
const before = await readIfExists(file);
|
|
61
|
+
if (before === null)
|
|
62
|
+
return result(this.id, []);
|
|
63
|
+
const doc = parseToml(before);
|
|
64
|
+
deleteIn(doc, ['model_providers', opts.providerId]);
|
|
65
|
+
if (doc.model_provider === opts.providerId)
|
|
66
|
+
delete doc.model_provider;
|
|
67
|
+
const change = await writeFileSafely(file, stringifyToml(doc), { dryRun: opts.dryRun });
|
|
68
|
+
return result(this.id, [change], { notes: [`Removed provider "${opts.providerId}" from ${shortenHome(file)}.`] });
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
//# sourceMappingURL=codex.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codex.js","sourceRoot":"","sources":["../../src/adapters/codex.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Adapter } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Continue (VS Code / JetBrains). `models` is a list, not a map, so we replace
|
|
4
|
+
* entries whose `provider` is ours and keep everything else the user added.
|
|
5
|
+
*/
|
|
6
|
+
export declare const continueAdapter: Adapter;
|
|
7
|
+
//# sourceMappingURL=continue.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"continue.d.ts","sourceRoot":"","sources":["../../src/adapters/continue.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAA6B,MAAM,aAAa,CAAC;AAUtE;;;GAGG;AACH,eAAO,MAAM,eAAe,EAAE,OAqE7B,CAAC"}
|