@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.
Files changed (2) hide show
  1. package/dist/index.js +296 -74
  2. 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.18",
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
- tier: exports_external.enum(["free", "paid"]).optional()
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
- async function readOpenCodeCliAuthKey() {
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
- const record2 = json3;
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?.Authorization) {
57725
- headers.set("Authorization", opts.authHeaders.Authorization);
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 (!opts.disableThinking || !init?.body || typeof init.body !== "string") {
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(init.body);
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 body = { ...parsed, thinking: { type: "disabled" } };
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
- function isOpenCodeFreeModel(id) {
96243
- const normalized = id.trim().toLowerCase();
96244
- return OPENCODE_FREE_MODEL_IDS.has(normalized) || normalized.endsWith("-free");
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(id) {
96439
+ function toOpenCodeModelEntry(slug) {
96440
+ const trimmed = slug.trim();
96441
+ const { providerId, modelId } = parseOpenCodeModelSlug(trimmed);
96256
96442
  return {
96257
- id,
96258
- name: humanizeModelId(id),
96259
- tier: isOpenCodeFreeModel(id) ? "free" : "paid"
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
- const trimmed = slug.trim();
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(stripOpenCodeModelSlug(item.id)));
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
- const id = stripOpenCodeModelSlug(slug);
96314
- if (!id || seen.has(id))
96495
+ if (seen.has(slug))
96315
96496
  continue;
96316
- seen.add(id);
96317
- models.push(toOpenCodeModelEntry(id));
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 vision runs.",
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: ["OPENCODE_API_KEY", "OPENCODE_ZEN_API_KEY", "OPENCODE_SERVER_PASSWORD"],
96331
- loginInstructions: "Paste a Zen API key from https://opencode.ai for vision. Local `opencode serve` is not OpenAI-compatible.",
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
- const apiKey = await resolveOpenAiCompatibleKey(auth);
96338
- if (!apiKey) {
96339
- 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.");
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
- const baseURL = auth.baseUrl?.trim().replace(/\/$/, "") || "https://opencode.ai/zen/v1";
96342
- return createOpenAI({
96343
- apiKey,
96344
- baseURL,
96345
- name: "opencode",
96346
- fetch: withOpenCodeRequestHooks({
96347
- disableThinking: true,
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(stripOpenCodeModelSlug(id))),
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 homedir10 } from "os";
96547
- import { dirname as dirname3, join as join13 } from "path";
96548
- var YOQA_ROOT5 = join13(homedir10(), ".yoqa");
96549
- var KEY_PATH = join13(YOQA_ROOT5, ".provider-key");
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yoqa/runner",
3
- "version": "0.3.18",
3
+ "version": "0.3.19",
4
4
  "description": "Yoqa local runner — Appium + catalog HTTP API (Bun). Start with yoqa serve or the yoqa-runner bin.",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",