@tokagent/tokagentos 2.0.31 → 2.0.33
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/package.json
CHANGED
|
@@ -114,9 +114,124 @@ for (const check of CHECKS) {
|
|
|
114
114
|
pass(`${check.pkg}@${installedVersion} ✓ (entry=${check.entry}, lines=${lineCount})`);
|
|
115
115
|
}
|
|
116
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Model-id sanity check.
|
|
119
|
+
*
|
|
120
|
+
* Failure mode observed in the field: a user "fixes" `OPENROUTER_SMALL_MODEL`
|
|
121
|
+
* by stripping the `anthropic/` prefix or by using a version that doesn't
|
|
122
|
+
* exist on OpenRouter. The plugin sends the id verbatim, OpenRouter returns
|
|
123
|
+
* an error event the AI SDK can't translate to a message, and the symptom
|
|
124
|
+
* is the opaque `AI_NoOutputGeneratedError: No output generated`. The error
|
|
125
|
+
* never mentions the model id, so users blame the plugin or the API key
|
|
126
|
+
* (both fine) and spend hours debugging.
|
|
127
|
+
*
|
|
128
|
+
* We only inspect the .env — `runtime.getSetting()` is a process.env wrapper,
|
|
129
|
+
* so the .env value is what the plugin will see. We don't hit the network;
|
|
130
|
+
* we just enforce the `<provider>/<model>` shape and warn if a known-bad
|
|
131
|
+
* id is set. If OPENROUTER_API_KEY is empty the user isn't using OpenRouter,
|
|
132
|
+
* so the check is skipped.
|
|
133
|
+
*/
|
|
134
|
+
function readDotenv() {
|
|
135
|
+
const dotenvPath = path.join(PROJECT_ROOT, ".env");
|
|
136
|
+
if (!existsSync(dotenvPath)) return {};
|
|
137
|
+
const out = {};
|
|
138
|
+
for (const rawLine of readFileSync(dotenvPath, "utf8").split("\n")) {
|
|
139
|
+
const line = rawLine.trim();
|
|
140
|
+
if (!line || line.startsWith("#")) continue;
|
|
141
|
+
const eq = line.indexOf("=");
|
|
142
|
+
if (eq < 1) continue;
|
|
143
|
+
out[line.slice(0, eq).trim()] = line
|
|
144
|
+
.slice(eq + 1)
|
|
145
|
+
.trim()
|
|
146
|
+
.replace(/^["']|["']$/g, "");
|
|
147
|
+
}
|
|
148
|
+
return out;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const env = readDotenv();
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Shell-env vs .env conflict check.
|
|
155
|
+
*
|
|
156
|
+
* Failure mode observed in the field: user rotates an API key in .env, but
|
|
157
|
+
* an old value is still exported in their shell from a prior `export` or a
|
|
158
|
+
* line in ~/.zshrc / ~/.bashrc. dotenv defaults to `override: false`, which
|
|
159
|
+
* means existing process.env values are NOT overwritten by .env. The agent
|
|
160
|
+
* sees the stale shell value, OpenRouter returns 401 "User not found", and
|
|
161
|
+
* the AI SDK surfaces the opaque `AI_NoOutputGeneratedError`. Changing the
|
|
162
|
+
* .env does nothing because the shell value still wins.
|
|
163
|
+
*
|
|
164
|
+
* We don't flip dotenv's `override` flag — that breaks legitimate CI/devops
|
|
165
|
+
* setups that intentionally pass secrets via env vars. Instead we detect
|
|
166
|
+
* the mismatch and tell the user which value will actually be used and how
|
|
167
|
+
* to fix it.
|
|
168
|
+
*/
|
|
169
|
+
const SHADOWED_KEYS = [
|
|
170
|
+
"OPENROUTER_API_KEY",
|
|
171
|
+
"ANTHROPIC_API_KEY",
|
|
172
|
+
"OPENAI_API_KEY",
|
|
173
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
174
|
+
"GROQ_API_KEY",
|
|
175
|
+
"TAVILY_API_KEY",
|
|
176
|
+
];
|
|
177
|
+
for (const key of SHADOWED_KEYS) {
|
|
178
|
+
const dotenvValue = env[key];
|
|
179
|
+
const shellValue = process.env[key];
|
|
180
|
+
if (dotenvValue && shellValue && dotenvValue !== shellValue) {
|
|
181
|
+
fail(
|
|
182
|
+
`${key} differs between your shell environment and .env.\n` +
|
|
183
|
+
` shell : ${shellValue.slice(0, 12)}…${shellValue.slice(-4)} (this is what the agent reads)\n` +
|
|
184
|
+
` .env : ${dotenvValue.slice(0, 12)}…${dotenvValue.slice(-4)} (this is what you probably edited)\n` +
|
|
185
|
+
` dotenv runs with override:false by default, so the shell value wins. To fix:\n` +
|
|
186
|
+
` unset ${key} # remove from the current shell\n` +
|
|
187
|
+
` grep -n ${key} ~/.zshrc ~/.zprofile ~/.bashrc ~/.bash_profile 2>/dev/null\n` +
|
|
188
|
+
` # delete any matching lines, restart the shell, then re-run \`bun run dev\``,
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
if (env.OPENROUTER_API_KEY) {
|
|
194
|
+
const MODEL_KEYS = [
|
|
195
|
+
"OPENROUTER_SMALL_MODEL",
|
|
196
|
+
"OPENROUTER_LARGE_MODEL",
|
|
197
|
+
"OPENROUTER_IMAGE_MODEL",
|
|
198
|
+
"OPENROUTER_IMAGE_GENERATION_MODEL",
|
|
199
|
+
"OPENROUTER_EMBEDDING_MODEL",
|
|
200
|
+
];
|
|
201
|
+
// Provider-namespaced model id. OpenRouter ids are `<provider>/<model>`
|
|
202
|
+
// (e.g. anthropic/claude-haiku-4.5, google/gemini-2.0-flash-001).
|
|
203
|
+
const validIdShape = /^[a-z0-9._-]+\/[a-z0-9._-]+(:[a-z0-9._-]+)?$/i;
|
|
204
|
+
for (const key of MODEL_KEYS) {
|
|
205
|
+
const value = env[key];
|
|
206
|
+
if (!value) continue;
|
|
207
|
+
if (!validIdShape.test(value)) {
|
|
208
|
+
fail(
|
|
209
|
+
`${key}=${value} is not a valid OpenRouter model id.\n` +
|
|
210
|
+
` OpenRouter requires "<provider>/<model>" (e.g. anthropic/claude-haiku-4.5).\n` +
|
|
211
|
+
` Bad ids surface as AI_NoOutputGeneratedError — the request leaves your machine,\n` +
|
|
212
|
+
` OpenRouter rejects the model, the AI SDK can't translate the error event, and you\n` +
|
|
213
|
+
` see "No output generated" with no hint about the model.\n` +
|
|
214
|
+
` Catalog: https://openrouter.ai/api/v1/models`,
|
|
215
|
+
);
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
// Catch the specific typo we shipped pre-2.0.31 (claude-haiku-4-5 with
|
|
219
|
+
// a hyphen instead of a dot). OpenRouter uses dotted version suffixes.
|
|
220
|
+
if (/\/claude-(haiku|sonnet|opus)-\d+-\d/i.test(value)) {
|
|
221
|
+
fail(
|
|
222
|
+
`${key}=${value} uses hyphens in the version suffix. OpenRouter uses dots — e.g.\n` +
|
|
223
|
+
` anthropic/claude-haiku-4.5, anthropic/claude-sonnet-4.6, anthropic/claude-opus-4.1.\n` +
|
|
224
|
+
` The hyphen form is silently rejected and surfaces as AI_NoOutputGeneratedError.`,
|
|
225
|
+
);
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
pass(`${key}=${value} ✓ (shape valid)`);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
117
232
|
if (hadFailure) {
|
|
118
233
|
console.error(
|
|
119
|
-
"\n\x1b[31m[verify-llm-plugins]\x1b[0m One or more LLM-provider
|
|
234
|
+
"\n\x1b[31m[verify-llm-plugins]\x1b[0m One or more LLM-provider checks failed. Chat will not work until this is resolved.\n",
|
|
120
235
|
);
|
|
121
236
|
process.exit(1);
|
|
122
237
|
}
|