@myvillage/cli 1.80.0 → 1.81.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/package.json +1 -1
- package/src/agent-runtime/loop.js +29 -3
package/package.json
CHANGED
|
@@ -64,7 +64,9 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
64
64
|
}
|
|
65
65
|
const apiKey = process.env.SELF_HOSTED_API_KEY || globalConfig.selfHostedApiKey || 'not-needed';
|
|
66
66
|
const openai = createOpenAI({ apiKey, baseURL });
|
|
67
|
-
|
|
67
|
+
// Most OpenAI-compatible servers (vLLM, Ollama, LM Studio, Baseten) serve
|
|
68
|
+
// /chat/completions but not the Responses API that openai(id) defaults to.
|
|
69
|
+
model = openai.chat(modelId);
|
|
68
70
|
} else if (provider === 'myvillage') {
|
|
69
71
|
// MyVillage-hosted models via the AI Gateway. OpenAI-compatible; the villager's
|
|
70
72
|
// OAuth token is the credential, and MVTs are deducted server-side by usage.
|
|
@@ -74,8 +76,32 @@ export async function agentLoop(agentName, { signal }) {
|
|
|
74
76
|
throw new Error('Not logged in. Run `myvillage login` to use MyVillage-hosted models.');
|
|
75
77
|
}
|
|
76
78
|
const { apiBaseUrl } = getConfig();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
+
// The model client is built once, but OAuth tokens rotate (~1h). A static
|
|
80
|
+
// bearer would 401 on every gateway call after expiry for long-lived agents,
|
|
81
|
+
// so inject the current token per request — refreshing first if it expired.
|
|
82
|
+
// Tell the gateway which agent is spending MVTs, so the server can attribute the
|
|
83
|
+
// MVT transaction (e.g. "AI chat · Vee 1.3 · agent: research-bot"). Header values
|
|
84
|
+
// must be safe ASCII — undici rejects CR/LF and non-latin1 — so strip aggressively.
|
|
85
|
+
const agentHeader = String(config.display_name || agentName || '')
|
|
86
|
+
.replace(/[^\x20-\x7e]+/g, ' ')
|
|
87
|
+
.replace(/\s+/g, ' ')
|
|
88
|
+
.trim()
|
|
89
|
+
.slice(0, 64);
|
|
90
|
+
const gatewayFetch = async (url, init = {}) => {
|
|
91
|
+
if (isTokenExpired()) {
|
|
92
|
+
try { await refreshAccessToken(); } catch { /* fall through with the existing token */ }
|
|
93
|
+
}
|
|
94
|
+
const current = getAccessToken();
|
|
95
|
+
const headers = new Headers(init.headers);
|
|
96
|
+
if (current) headers.set('authorization', `Bearer ${current}`);
|
|
97
|
+
if (agentHeader) headers.set('x-myvillage-agent', agentHeader);
|
|
98
|
+
return fetch(url, { ...init, headers });
|
|
99
|
+
};
|
|
100
|
+
const openai = createOpenAI({ apiKey: token, baseURL: `${apiBaseUrl}/ai`, fetch: gatewayFetch });
|
|
101
|
+
// The AI Gateway implements the OpenAI Chat Completions API (/chat/completions).
|
|
102
|
+
// @ai-sdk/openai's default `openai(id)` targets the newer Responses API
|
|
103
|
+
// (/responses), which the gateway does not serve — so pin to chat explicitly.
|
|
104
|
+
model = openai.chat(modelId);
|
|
79
105
|
} else {
|
|
80
106
|
const apiKey = process.env.ANTHROPIC_API_KEY || globalConfig.anthropicApiKey;
|
|
81
107
|
if (!apiKey) {
|