@yoqa/runner 0.3.18 → 0.3.19
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/dist/index.js +296 -74
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16801,7 +16801,7 @@ function getCatalogDb() {
|
|
|
16801
16801
|
// package.json
|
|
16802
16802
|
var package_default = {
|
|
16803
16803
|
name: "@yoqa/runner",
|
|
16804
|
-
version: "0.3.
|
|
16804
|
+
version: "0.3.19",
|
|
16805
16805
|
description: "Yoqa local runner \u2014 Appium + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
|
|
16806
16806
|
license: "UNLICENSED",
|
|
16807
16807
|
type: "module",
|
|
@@ -24258,7 +24258,7 @@ var probeProviderResponseSchema = exports_external.object({
|
|
|
24258
24258
|
var providerModelSchema = exports_external.object({
|
|
24259
24259
|
id: exports_external.string().min(1),
|
|
24260
24260
|
name: exports_external.string().min(1),
|
|
24261
|
-
|
|
24261
|
+
provider: exports_external.string().min(1).optional()
|
|
24262
24262
|
});
|
|
24263
24263
|
var listProviderModelsResponseSchema = exports_external.object({
|
|
24264
24264
|
models: exports_external.array(providerModelSchema),
|
|
@@ -57545,6 +57545,9 @@ function formatProviderHttpError(label, status, body) {
|
|
|
57545
57545
|
if (label === "OpenCode" && (body.includes("unknown variant `image_url`") || body.includes("No endpoints found that support image input") || body.includes("expected `text`"))) {
|
|
57546
57546
|
return `This OpenCode model does not accept screenshots (text-only). In Settings \u2192 Provider, set the default model to ${EXAMPLE_OPENCODE_VISION_MODEL} (or another vision-capable model). deepseek-v4-flash-free and big-pickle are text-only on Zen.`;
|
|
57547
57547
|
}
|
|
57548
|
+
if (label === "OpenCode" && (body.includes("output_config.format") || body.includes("BedrockException") && body.includes("Extra inputs are not permitted"))) {
|
|
57549
|
+
return "LiteLLM/Bedrock rejected JSON schema mode for this model (output_config.format). Restart the Yoqa runner and retry \u2014 LiteLLM vision now asks for JSON in the prompt only.";
|
|
57550
|
+
}
|
|
57548
57551
|
if (label === "OpenCode" && status >= 500 && (body.includes("Internal server error") || body.includes('"type":"error"'))) {
|
|
57549
57552
|
return `OpenCode Zen returned an internal error for this vision request. Many free models reject screenshots. In Settings \u2192 Provider, set the default model to ${EXAMPLE_OPENCODE_VISION_MODEL} (or another vision-capable model).`;
|
|
57550
57553
|
}
|
|
@@ -57578,6 +57581,16 @@ function mapProviderError(label, error40) {
|
|
|
57578
57581
|
}
|
|
57579
57582
|
throw new AgentProviderError(String(error40));
|
|
57580
57583
|
}
|
|
57584
|
+
function repairModelJsonText(text3) {
|
|
57585
|
+
const trimmed = text3.trim();
|
|
57586
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
57587
|
+
const candidate = fenced?.[1]?.trim() ?? trimmed;
|
|
57588
|
+
const start = candidate.indexOf("{");
|
|
57589
|
+
const end = candidate.lastIndexOf("}");
|
|
57590
|
+
if (start < 0 || end <= start)
|
|
57591
|
+
return trimmed;
|
|
57592
|
+
return candidate.slice(start, end + 1);
|
|
57593
|
+
}
|
|
57581
57594
|
async function completeWithAiSdk(input) {
|
|
57582
57595
|
const run = async (prompt) => {
|
|
57583
57596
|
try {
|
|
@@ -57586,6 +57599,7 @@ async function completeWithAiSdk(input) {
|
|
|
57586
57599
|
schema: input.schema,
|
|
57587
57600
|
system: input.system,
|
|
57588
57601
|
maxOutputTokens: VISION_MAX_TOKENS,
|
|
57602
|
+
experimental_repairText: async ({ text: text3 }) => repairModelJsonText(text3),
|
|
57589
57603
|
messages: [
|
|
57590
57604
|
{
|
|
57591
57605
|
role: "user",
|
|
@@ -57602,6 +57616,11 @@ async function completeWithAiSdk(input) {
|
|
|
57602
57616
|
});
|
|
57603
57617
|
return object3;
|
|
57604
57618
|
} catch (error40) {
|
|
57619
|
+
if (NoObjectGeneratedError.isInstance(error40) && error40.text?.trim()) {
|
|
57620
|
+
try {
|
|
57621
|
+
return input.schema.parse(JSON.parse(repairModelJsonText(error40.text)));
|
|
57622
|
+
} catch {}
|
|
57623
|
+
}
|
|
57605
57624
|
mapProviderError(input.label, error40);
|
|
57606
57625
|
}
|
|
57607
57626
|
};
|
|
@@ -57644,41 +57663,58 @@ function openCodeAuthPaths() {
|
|
|
57644
57663
|
paths.push(join9(homedir8(), ".local", "share", "opencode", "auth.json"));
|
|
57645
57664
|
return paths;
|
|
57646
57665
|
}
|
|
57647
|
-
|
|
57666
|
+
function apiKeyFromOpenCodeAuthEntry(value) {
|
|
57667
|
+
if (!value || typeof value !== "object")
|
|
57668
|
+
return null;
|
|
57669
|
+
const entry = value;
|
|
57670
|
+
for (const field of ["key", "apiKey", "token"]) {
|
|
57671
|
+
const candidate = entry[field];
|
|
57672
|
+
if (typeof candidate === "string" && candidate.trim())
|
|
57673
|
+
return candidate.trim();
|
|
57674
|
+
}
|
|
57675
|
+
return null;
|
|
57676
|
+
}
|
|
57677
|
+
async function readOpenCodeCliAuthJson() {
|
|
57648
57678
|
for (const path of openCodeAuthPaths()) {
|
|
57649
57679
|
try {
|
|
57650
57680
|
const file2 = Bun.file(path);
|
|
57651
57681
|
if (!await file2.exists())
|
|
57652
57682
|
continue;
|
|
57653
57683
|
const json3 = await file2.json();
|
|
57654
|
-
if (!json3 || typeof json3 !== "object")
|
|
57684
|
+
if (!json3 || typeof json3 !== "object" || Array.isArray(json3))
|
|
57655
57685
|
continue;
|
|
57656
|
-
|
|
57657
|
-
const opencode = record2.opencode;
|
|
57658
|
-
if (opencode && typeof opencode === "object") {
|
|
57659
|
-
const entry = opencode;
|
|
57660
|
-
for (const field of ["key", "apiKey", "token"]) {
|
|
57661
|
-
const value = entry[field];
|
|
57662
|
-
if (typeof value === "string" && value.trim())
|
|
57663
|
-
return value.trim();
|
|
57664
|
-
}
|
|
57665
|
-
}
|
|
57666
|
-
for (const value of Object.values(record2)) {
|
|
57667
|
-
if (!value || typeof value !== "object")
|
|
57668
|
-
continue;
|
|
57669
|
-
const entry = value;
|
|
57670
|
-
const key = entry.key ?? entry.apiKey ?? entry.token;
|
|
57671
|
-
if (typeof key === "string" && key.trim()) {
|
|
57672
|
-
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
|
|
57673
|
-
if (!type || type.includes("api") || type.includes("key") || type === "opencode") {
|
|
57674
|
-
return key.trim();
|
|
57675
|
-
}
|
|
57676
|
-
}
|
|
57677
|
-
}
|
|
57686
|
+
return json3;
|
|
57678
57687
|
} catch {}
|
|
57679
57688
|
}
|
|
57680
57689
|
return null;
|
|
57681
57690
|
}
|
|
57691
|
+
async function readOpenCodeCliAuthKeyForProvider(providerId) {
|
|
57692
|
+
const record2 = await readOpenCodeCliAuthJson();
|
|
57693
|
+
if (!record2)
|
|
57694
|
+
return null;
|
|
57695
|
+
return apiKeyFromOpenCodeAuthEntry(record2[providerId]);
|
|
57696
|
+
}
|
|
57697
|
+
async function readOpenCodeCliAuthKey() {
|
|
57698
|
+
const record2 = await readOpenCodeCliAuthJson();
|
|
57699
|
+
if (!record2)
|
|
57700
|
+
return null;
|
|
57701
|
+
const zen = apiKeyFromOpenCodeAuthEntry(record2.opencode);
|
|
57702
|
+
if (zen)
|
|
57703
|
+
return zen;
|
|
57704
|
+
for (const value of Object.values(record2)) {
|
|
57705
|
+
const key = apiKeyFromOpenCodeAuthEntry(value);
|
|
57706
|
+
if (!key)
|
|
57707
|
+
continue;
|
|
57708
|
+
if (!value || typeof value !== "object")
|
|
57709
|
+
continue;
|
|
57710
|
+
const entry = value;
|
|
57711
|
+
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
|
|
57712
|
+
if (!type || type.includes("api") || type.includes("key") || type === "opencode") {
|
|
57713
|
+
return key;
|
|
57714
|
+
}
|
|
57715
|
+
}
|
|
57716
|
+
return null;
|
|
57717
|
+
}
|
|
57682
57718
|
async function resolveOpenAiCompatibleKey(auth) {
|
|
57683
57719
|
if (auth.apiKey?.trim())
|
|
57684
57720
|
return auth.apiKey.trim();
|
|
@@ -57721,18 +57757,34 @@ function withOpenCodeRequestHooks(opts) {
|
|
|
57721
57757
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
57722
57758
|
return async (input, init) => {
|
|
57723
57759
|
const headers = new Headers(init?.headers);
|
|
57724
|
-
if (opts.authHeaders
|
|
57725
|
-
|
|
57760
|
+
if (opts.authHeaders) {
|
|
57761
|
+
for (const [key, value] of Object.entries(opts.authHeaders)) {
|
|
57762
|
+
if (value)
|
|
57763
|
+
headers.set(key, value);
|
|
57764
|
+
}
|
|
57726
57765
|
}
|
|
57727
|
-
if (
|
|
57766
|
+
if (opts.extraHeaders) {
|
|
57767
|
+
for (const [key, value] of Object.entries(opts.extraHeaders)) {
|
|
57768
|
+
if (value)
|
|
57769
|
+
headers.set(key, value);
|
|
57770
|
+
}
|
|
57771
|
+
}
|
|
57772
|
+
const rawBody = init?.body;
|
|
57773
|
+
const shouldRewriteBody = (opts.disableThinking || Boolean(opts.stripResponseFormat)) && typeof rawBody === "string";
|
|
57774
|
+
if (!shouldRewriteBody || typeof rawBody !== "string") {
|
|
57728
57775
|
return fetchImpl(input, { ...init, headers });
|
|
57729
57776
|
}
|
|
57730
57777
|
try {
|
|
57731
|
-
const parsed = JSON.parse(
|
|
57778
|
+
const parsed = JSON.parse(rawBody);
|
|
57732
57779
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
57733
57780
|
return fetchImpl(input, { ...init, headers });
|
|
57734
57781
|
}
|
|
57735
|
-
const
|
|
57782
|
+
const parsedBody = { ...parsed };
|
|
57783
|
+
if (opts.disableThinking) {
|
|
57784
|
+
parsedBody.thinking = { type: "disabled" };
|
|
57785
|
+
}
|
|
57786
|
+
const { response_format: _responseFormat, ...withoutFormat } = parsedBody;
|
|
57787
|
+
const body = opts.stripResponseFormat ? withoutFormat : parsedBody;
|
|
57736
57788
|
return fetchImpl(input, {
|
|
57737
57789
|
...init,
|
|
57738
57790
|
headers,
|
|
@@ -96236,12 +96288,144 @@ var openaiDriver = {
|
|
|
96236
96288
|
}
|
|
96237
96289
|
};
|
|
96238
96290
|
|
|
96291
|
+
// src/domains/providers/drivers/opencode-config.ts
|
|
96292
|
+
import { homedir as homedir10 } from "os";
|
|
96293
|
+
import { join as join13 } from "path";
|
|
96294
|
+
var OPENAI_COMPATIBLE_PACKAGES = new Set([
|
|
96295
|
+
"@ai-sdk/openai-compatible",
|
|
96296
|
+
"@ai-sdk/openai",
|
|
96297
|
+
"@ai-sdk/azure"
|
|
96298
|
+
]);
|
|
96299
|
+
function asRecord3(value) {
|
|
96300
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
96301
|
+
return null;
|
|
96302
|
+
return value;
|
|
96303
|
+
}
|
|
96304
|
+
function isOpenAiCompatibleNpm(npm) {
|
|
96305
|
+
const trimmed = npm.trim();
|
|
96306
|
+
if (!trimmed)
|
|
96307
|
+
return true;
|
|
96308
|
+
if (OPENAI_COMPATIBLE_PACKAGES.has(trimmed))
|
|
96309
|
+
return true;
|
|
96310
|
+
return trimmed.includes("openai-compatible");
|
|
96311
|
+
}
|
|
96312
|
+
function interpolateOpenCodeTemplate(value, env) {
|
|
96313
|
+
return value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_full, name31) => {
|
|
96314
|
+
return env[name31]?.trim() || process.env[name31]?.trim() || "";
|
|
96315
|
+
}).trim();
|
|
96316
|
+
}
|
|
96317
|
+
function openCodeConfigPaths() {
|
|
96318
|
+
const paths = [];
|
|
96319
|
+
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
96320
|
+
if (xdg)
|
|
96321
|
+
paths.push(join13(xdg, "opencode", "opencode.json"));
|
|
96322
|
+
paths.push(join13(homedir10(), ".config", "opencode", "opencode.json"));
|
|
96323
|
+
return paths;
|
|
96324
|
+
}
|
|
96325
|
+
function parseOpenCodeCompatibleProvider(config2, providerId) {
|
|
96326
|
+
const root = asRecord3(config2);
|
|
96327
|
+
const providers2 = asRecord3(root?.provider);
|
|
96328
|
+
const entry = asRecord3(providers2?.[providerId]);
|
|
96329
|
+
if (!entry)
|
|
96330
|
+
return null;
|
|
96331
|
+
const npm = typeof entry.npm === "string" ? entry.npm : "";
|
|
96332
|
+
if (!isOpenAiCompatibleNpm(npm))
|
|
96333
|
+
return null;
|
|
96334
|
+
const options = asRecord3(entry.options);
|
|
96335
|
+
const baseURLRaw = typeof options?.baseURL === "string" ? options.baseURL.trim() : "";
|
|
96336
|
+
if (!baseURLRaw)
|
|
96337
|
+
return null;
|
|
96338
|
+
const apiKeyTemplate = typeof options?.apiKey === "string" ? options.apiKey : null;
|
|
96339
|
+
const headers = {};
|
|
96340
|
+
const headerBag = asRecord3(options?.headers);
|
|
96341
|
+
if (headerBag) {
|
|
96342
|
+
for (const [key, value] of Object.entries(headerBag)) {
|
|
96343
|
+
if (typeof value === "string" && value.trim())
|
|
96344
|
+
headers[key] = value;
|
|
96345
|
+
}
|
|
96346
|
+
}
|
|
96347
|
+
return {
|
|
96348
|
+
providerId,
|
|
96349
|
+
baseURL: baseURLRaw.replace(/\/$/, ""),
|
|
96350
|
+
apiKeyTemplate,
|
|
96351
|
+
headers
|
|
96352
|
+
};
|
|
96353
|
+
}
|
|
96354
|
+
function resolveOpenCodeCompatibleAuth(input) {
|
|
96355
|
+
const parsed = parseOpenCodeCompatibleProvider(input.config, input.providerId);
|
|
96356
|
+
const override = input.overrideBaseUrl?.trim().replace(/\/$/, "") || null;
|
|
96357
|
+
if (!parsed && !(override && input.providerId === "litellm"))
|
|
96358
|
+
return null;
|
|
96359
|
+
const baseURL = override || parsed?.baseURL || null;
|
|
96360
|
+
if (!baseURL)
|
|
96361
|
+
return null;
|
|
96362
|
+
const headers = {};
|
|
96363
|
+
if (parsed) {
|
|
96364
|
+
for (const [key, value] of Object.entries(parsed.headers)) {
|
|
96365
|
+
const interpolated = interpolateOpenCodeTemplate(value, input.env);
|
|
96366
|
+
if (interpolated)
|
|
96367
|
+
headers[key] = interpolated;
|
|
96368
|
+
}
|
|
96369
|
+
}
|
|
96370
|
+
const fromTemplate = parsed?.apiKeyTemplate ? interpolateOpenCodeTemplate(parsed.apiKeyTemplate, input.env) : "";
|
|
96371
|
+
const envKeyName = `${input.providerId.replace(/-/g, "_").toUpperCase()}_API_KEY`;
|
|
96372
|
+
const apiKey = fromTemplate || input.env[envKeyName]?.trim() || process.env[envKeyName]?.trim() || input.storedKey?.trim() || null;
|
|
96373
|
+
return { baseURL, apiKey, headers };
|
|
96374
|
+
}
|
|
96375
|
+
async function loadOpenCodeUserConfig() {
|
|
96376
|
+
for (const path of openCodeConfigPaths()) {
|
|
96377
|
+
try {
|
|
96378
|
+
const file2 = Bun.file(path);
|
|
96379
|
+
if (!await file2.exists())
|
|
96380
|
+
continue;
|
|
96381
|
+
return await file2.json();
|
|
96382
|
+
} catch {}
|
|
96383
|
+
}
|
|
96384
|
+
return null;
|
|
96385
|
+
}
|
|
96386
|
+
|
|
96239
96387
|
// src/domains/providers/drivers/opencode.ts
|
|
96240
|
-
var OPENCODE_FREE_MODEL_IDS = new Set(["big-pickle"]);
|
|
96241
96388
|
var OPENCODE_DEFAULT_VISION_MODEL = "mimo-v2.5-free";
|
|
96242
|
-
|
|
96243
|
-
|
|
96244
|
-
|
|
96389
|
+
var OPENCODE_PROVIDER_LABELS = {
|
|
96390
|
+
"amazon-bedrock": "Amazon Bedrock",
|
|
96391
|
+
anthropic: "Anthropic",
|
|
96392
|
+
azure: "Azure OpenAI",
|
|
96393
|
+
"github-copilot": "GitHub Copilot",
|
|
96394
|
+
google: "Google",
|
|
96395
|
+
"google-vertex": "Google Vertex AI",
|
|
96396
|
+
groq: "Groq",
|
|
96397
|
+
litellm: "LiteLLM",
|
|
96398
|
+
ollama: "Ollama",
|
|
96399
|
+
openai: "OpenAI",
|
|
96400
|
+
opencode: "OpenCode Zen",
|
|
96401
|
+
"opencode-go": "OpenCode Go",
|
|
96402
|
+
openrouter: "OpenRouter",
|
|
96403
|
+
xai: "xAI",
|
|
96404
|
+
zenmux: "ZenMux"
|
|
96405
|
+
};
|
|
96406
|
+
function humanizeOpenCodeProvider(providerId) {
|
|
96407
|
+
const known = OPENCODE_PROVIDER_LABELS[providerId];
|
|
96408
|
+
if (known)
|
|
96409
|
+
return known;
|
|
96410
|
+
return providerId.split(/[-_]/).filter(Boolean).map((part) => {
|
|
96411
|
+
if (part.toLowerCase() === "ai")
|
|
96412
|
+
return "AI";
|
|
96413
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
96414
|
+
}).join(" ");
|
|
96415
|
+
}
|
|
96416
|
+
function parseOpenCodeModelSlug(slug) {
|
|
96417
|
+
const trimmed = slug.trim();
|
|
96418
|
+
const sep = trimmed.indexOf("/");
|
|
96419
|
+
if (sep <= 0 || sep === trimmed.length - 1) {
|
|
96420
|
+
return { providerId: "opencode", modelId: trimmed };
|
|
96421
|
+
}
|
|
96422
|
+
return {
|
|
96423
|
+
providerId: trimmed.slice(0, sep),
|
|
96424
|
+
modelId: trimmed.slice(sep + 1)
|
|
96425
|
+
};
|
|
96426
|
+
}
|
|
96427
|
+
function isOpenCodeZenModel(slug) {
|
|
96428
|
+
return parseOpenCodeModelSlug(slug).providerId === "opencode";
|
|
96245
96429
|
}
|
|
96246
96430
|
function humanizeModelId(id) {
|
|
96247
96431
|
return id.split(/[-_]/).filter(Boolean).map((part) => {
|
|
@@ -96252,11 +96436,13 @@ function humanizeModelId(id) {
|
|
|
96252
96436
|
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
96253
96437
|
}).join(" ");
|
|
96254
96438
|
}
|
|
96255
|
-
function toOpenCodeModelEntry(
|
|
96439
|
+
function toOpenCodeModelEntry(slug) {
|
|
96440
|
+
const trimmed = slug.trim();
|
|
96441
|
+
const { providerId, modelId } = parseOpenCodeModelSlug(trimmed);
|
|
96256
96442
|
return {
|
|
96257
|
-
id,
|
|
96258
|
-
name: humanizeModelId(
|
|
96259
|
-
|
|
96443
|
+
id: trimmed,
|
|
96444
|
+
name: humanizeModelId(modelId),
|
|
96445
|
+
provider: humanizeOpenCodeProvider(providerId)
|
|
96260
96446
|
};
|
|
96261
96447
|
}
|
|
96262
96448
|
function openCodeServerAuthHeaders(password) {
|
|
@@ -96270,18 +96456,14 @@ function resolveOpenCodeServerPassword(env) {
|
|
|
96270
96456
|
return env.OPENCODE_SERVER_PASSWORD?.trim() || env.SERVER_PASSWORD?.trim() || process.env.OPENCODE_SERVER_PASSWORD?.trim() || process.env.SERVER_PASSWORD?.trim() || null;
|
|
96271
96457
|
}
|
|
96272
96458
|
function stripOpenCodeModelSlug(slug) {
|
|
96273
|
-
|
|
96274
|
-
const sep = trimmed.indexOf("/");
|
|
96275
|
-
if (sep <= 0 || sep === trimmed.length - 1)
|
|
96276
|
-
return trimmed;
|
|
96277
|
-
return trimmed.slice(sep + 1);
|
|
96459
|
+
return parseOpenCodeModelSlug(slug).modelId;
|
|
96278
96460
|
}
|
|
96279
96461
|
function parseModelsPayload(json3) {
|
|
96280
96462
|
const models = [];
|
|
96281
96463
|
if (json3 && typeof json3 === "object" && "data" in json3 && Array.isArray(json3.data)) {
|
|
96282
96464
|
for (const item of json3.data) {
|
|
96283
96465
|
if (item && typeof item === "object" && "id" in item && typeof item.id === "string") {
|
|
96284
|
-
models.push(toOpenCodeModelEntry(
|
|
96466
|
+
models.push(toOpenCodeModelEntry(item.id));
|
|
96285
96467
|
}
|
|
96286
96468
|
}
|
|
96287
96469
|
}
|
|
@@ -96310,44 +96492,84 @@ async function listOpenCodeModelsFromCli(binaryPath) {
|
|
|
96310
96492
|
const slug = line.trim();
|
|
96311
96493
|
if (!slug || /\s/.test(slug))
|
|
96312
96494
|
continue;
|
|
96313
|
-
|
|
96314
|
-
if (!id || seen.has(id))
|
|
96495
|
+
if (seen.has(slug))
|
|
96315
96496
|
continue;
|
|
96316
|
-
seen.add(
|
|
96317
|
-
models.push(toOpenCodeModelEntry(
|
|
96497
|
+
seen.add(slug);
|
|
96498
|
+
models.push(toOpenCodeModelEntry(slug));
|
|
96318
96499
|
}
|
|
96319
96500
|
return {
|
|
96320
96501
|
models,
|
|
96321
96502
|
detail: `${models.length} models from opencode CLI`
|
|
96322
96503
|
};
|
|
96323
96504
|
}
|
|
96505
|
+
async function resolveCompatibleOpenCodeVision(providerId, auth) {
|
|
96506
|
+
const config2 = await loadOpenCodeUserConfig();
|
|
96507
|
+
const storedKey = await readOpenCodeCliAuthKeyForProvider(providerId);
|
|
96508
|
+
return resolveOpenCodeCompatibleAuth({
|
|
96509
|
+
providerId,
|
|
96510
|
+
config: config2,
|
|
96511
|
+
env: auth.env,
|
|
96512
|
+
storedKey,
|
|
96513
|
+
overrideBaseUrl: auth.baseUrl
|
|
96514
|
+
});
|
|
96515
|
+
}
|
|
96324
96516
|
var opencodeDriver = {
|
|
96325
96517
|
kind: "opencode",
|
|
96326
96518
|
label: "OpenCode",
|
|
96327
|
-
description: "OpenCode Zen (OpenAI-compatible). Paste a Zen API key for
|
|
96519
|
+
description: "OpenCode Zen or LiteLLM (OpenAI-compatible). Paste a Zen API key for Zen; LiteLLM uses opencode.json.",
|
|
96328
96520
|
defaultBinary: "opencode",
|
|
96329
96521
|
authModes: ["cli", "api_key"],
|
|
96330
|
-
envHints: [
|
|
96331
|
-
|
|
96522
|
+
envHints: [
|
|
96523
|
+
"OPENCODE_API_KEY",
|
|
96524
|
+
"OPENCODE_ZEN_API_KEY",
|
|
96525
|
+
"OPENCODE_SERVER_PASSWORD",
|
|
96526
|
+
"LITELLM_API_KEY"
|
|
96527
|
+
],
|
|
96528
|
+
loginInstructions: "Paste a Zen API key from https://opencode.ai for Zen vision. LiteLLM uses ~/.config/opencode/opencode.json plus CLI auth or LITELLM_API_KEY. Local `opencode serve` is not OpenAI-compatible.",
|
|
96332
96529
|
capabilities: { vision: true },
|
|
96333
96530
|
vision: createSdkVisionPort({
|
|
96334
96531
|
label: "OpenCode",
|
|
96335
96532
|
defaultModel: OPENCODE_DEFAULT_VISION_MODEL,
|
|
96336
96533
|
createModel: async (auth, modelId) => {
|
|
96337
|
-
|
|
96338
|
-
|
|
96339
|
-
|
|
96534
|
+
if (isOpenCodeZenModel(modelId)) {
|
|
96535
|
+
const apiKey = await resolveOpenAiCompatibleKey(auth);
|
|
96536
|
+
if (!apiKey) {
|
|
96537
|
+
throw new AgentProviderError("OpenCode vision needs a Zen API key. Local `opencode serve` returns a web UI for /v1 (not chat completions). Paste a key from https://opencode.ai in Settings \u2192 Provider, or set OPENCODE_API_KEY.");
|
|
96538
|
+
}
|
|
96539
|
+
const baseURL = auth.baseUrl?.trim().replace(/\/$/, "") || "https://opencode.ai/zen/v1";
|
|
96540
|
+
return createOpenAI({
|
|
96541
|
+
apiKey,
|
|
96542
|
+
baseURL,
|
|
96543
|
+
name: "opencode",
|
|
96544
|
+
fetch: withOpenCodeRequestHooks({
|
|
96545
|
+
disableThinking: true,
|
|
96546
|
+
authHeaders: null
|
|
96547
|
+
})
|
|
96548
|
+
}).chat(stripOpenCodeModelSlug(modelId));
|
|
96549
|
+
}
|
|
96550
|
+
const { providerId } = parseOpenCodeModelSlug(modelId);
|
|
96551
|
+
const providerLabel = humanizeOpenCodeProvider(providerId);
|
|
96552
|
+
const compatible = await resolveCompatibleOpenCodeVision(providerId, auth);
|
|
96553
|
+
if (compatible?.apiKey) {
|
|
96554
|
+
return createOpenAI({
|
|
96555
|
+
apiKey: compatible.apiKey,
|
|
96556
|
+
baseURL: compatible.baseURL,
|
|
96557
|
+
name: providerId,
|
|
96558
|
+
fetch: withOpenCodeRequestHooks({
|
|
96559
|
+
disableThinking: false,
|
|
96560
|
+
stripResponseFormat: true,
|
|
96561
|
+
authHeaders: null,
|
|
96562
|
+
extraHeaders: Object.keys(compatible.headers).length > 0 ? compatible.headers : null
|
|
96563
|
+
})
|
|
96564
|
+
}).chat(stripOpenCodeModelSlug(modelId));
|
|
96340
96565
|
}
|
|
96341
|
-
|
|
96342
|
-
|
|
96343
|
-
|
|
96344
|
-
|
|
96345
|
-
|
|
96346
|
-
|
|
96347
|
-
|
|
96348
|
-
authHeaders: null
|
|
96349
|
-
})
|
|
96350
|
-
}).chat(modelId);
|
|
96566
|
+
if (compatible && !compatible.apiKey) {
|
|
96567
|
+
throw new AgentProviderError(`${providerLabel} vision needs an API key. OpenCode stores it in auth.json after login, or set ${providerId.replace(/-/g, "_").toUpperCase()}_API_KEY in Settings \u2192 Provider.`);
|
|
96568
|
+
}
|
|
96569
|
+
if (providerId === "litellm") {
|
|
96570
|
+
throw new AgentProviderError("Yoqa vision can use LiteLLM via OpenCode. Add provider.litellm.options.baseURL in ~/.config/opencode/opencode.json, then pick litellm/\u2026 in Settings \u2192 Provider.");
|
|
96571
|
+
}
|
|
96572
|
+
throw new AgentProviderError(`Yoqa vision uses OpenCode Zen or LiteLLM. "${modelId}" is a ${providerLabel} model. Pick an OpenCode Zen or LiteLLM model in Settings \u2192 Provider.`);
|
|
96351
96573
|
}
|
|
96352
96574
|
}),
|
|
96353
96575
|
async probe(binaryPath) {
|
|
@@ -96438,6 +96660,10 @@ var opencodeDriver = {
|
|
|
96438
96660
|
async listModels(input) {
|
|
96439
96661
|
const key = resolveOpenCodeKey(input);
|
|
96440
96662
|
const serverUrl = input.serverUrl?.trim() || null;
|
|
96663
|
+
const cli = await listOpenCodeModelsFromCli(input.binaryPath);
|
|
96664
|
+
if (cli.models.length > 0) {
|
|
96665
|
+
return { models: cli.models, message: cli.detail };
|
|
96666
|
+
}
|
|
96441
96667
|
if (serverUrl) {
|
|
96442
96668
|
try {
|
|
96443
96669
|
const password = resolveOpenCodeServerPassword(input.env);
|
|
@@ -96463,14 +96689,10 @@ var opencodeDriver = {
|
|
|
96463
96689
|
label: "OpenCode"
|
|
96464
96690
|
});
|
|
96465
96691
|
return {
|
|
96466
|
-
models: (result.models ?? []).map((id) => toOpenCodeModelEntry(
|
|
96692
|
+
models: (result.models ?? []).map((id) => toOpenCodeModelEntry(id)),
|
|
96467
96693
|
message: result.ok ? `${result.models?.length ?? 0} models available` : result.message
|
|
96468
96694
|
};
|
|
96469
96695
|
}
|
|
96470
|
-
const cli = await listOpenCodeModelsFromCli(input.binaryPath);
|
|
96471
|
-
if (cli.models.length > 0) {
|
|
96472
|
-
return { models: cli.models, message: cli.detail };
|
|
96473
|
-
}
|
|
96474
96696
|
return {
|
|
96475
96697
|
models: [],
|
|
96476
96698
|
message: "Add OPENCODE_API_KEY, a server URL, or install the opencode CLI to list models"
|
|
@@ -96543,10 +96765,10 @@ import {
|
|
|
96543
96765
|
randomBytes
|
|
96544
96766
|
} from "crypto";
|
|
96545
96767
|
import { chmodSync, existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "fs";
|
|
96546
|
-
import { homedir as
|
|
96547
|
-
import { dirname as dirname3, join as
|
|
96548
|
-
var YOQA_ROOT5 =
|
|
96549
|
-
var KEY_PATH =
|
|
96768
|
+
import { homedir as homedir11 } from "os";
|
|
96769
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
96770
|
+
var YOQA_ROOT5 = join14(homedir11(), ".yoqa");
|
|
96771
|
+
var KEY_PATH = join14(YOQA_ROOT5, ".provider-key");
|
|
96550
96772
|
var ALGORITHM = "aes-256-gcm";
|
|
96551
96773
|
var IV_LENGTH = 12;
|
|
96552
96774
|
var KEY_LENGTH = 32;
|
package/package.json
CHANGED