@yoqa/runner 0.3.18 → 0.3.20
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 +433 -109
- 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.20",
|
|
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",
|
|
@@ -17353,10 +17353,6 @@ var MJPEG_SETTINGS_BASE = {
|
|
|
17353
17353
|
mjpegScalingFactor: 50
|
|
17354
17354
|
};
|
|
17355
17355
|
var openByDeviceId = new Map;
|
|
17356
|
-
var exclusiveReleaseListener = null;
|
|
17357
|
-
function onDeviceSessionExclusiveRelease(listener) {
|
|
17358
|
-
exclusiveReleaseListener = listener;
|
|
17359
|
-
}
|
|
17360
17356
|
var DEAD_SESSION_RE = /session does not exist|invalid session id|no such session|terminated or not started|session is either terminated/i;
|
|
17361
17357
|
|
|
17362
17358
|
class DeadSessionError extends Error {
|
|
@@ -17441,7 +17437,6 @@ async function releaseExistingSession(deviceId) {
|
|
|
17441
17437
|
if (!existing)
|
|
17442
17438
|
return;
|
|
17443
17439
|
openByDeviceId.delete(deviceId);
|
|
17444
|
-
exclusiveReleaseListener?.(deviceId);
|
|
17445
17440
|
try {
|
|
17446
17441
|
await existing.quit();
|
|
17447
17442
|
} catch (error) {
|
|
@@ -17975,21 +17970,32 @@ async function createDeviceSession(options) {
|
|
|
17975
17970
|
|
|
17976
17971
|
// src/domains/devices/active-session.ts
|
|
17977
17972
|
var active = null;
|
|
17973
|
+
|
|
17974
|
+
class SessionBusyError extends Error {
|
|
17975
|
+
constructor(message) {
|
|
17976
|
+
super(message);
|
|
17977
|
+
this.name = "SessionBusyError";
|
|
17978
|
+
}
|
|
17979
|
+
}
|
|
17978
17980
|
var isMissingAppiumSessionError = isDeadSessionError;
|
|
17981
|
+
function toInfo(current) {
|
|
17982
|
+
return {
|
|
17983
|
+
deviceId: current.deviceId,
|
|
17984
|
+
platform: current.platform,
|
|
17985
|
+
connectedAt: current.connectedAt,
|
|
17986
|
+
mjpegPort: current.mjpegPort,
|
|
17987
|
+
streamReady: current.streamReady,
|
|
17988
|
+
streamUrl: current.streamUrl,
|
|
17989
|
+
heldByRun: current.heldByRunId != null
|
|
17990
|
+
};
|
|
17991
|
+
}
|
|
17979
17992
|
function getActiveSession() {
|
|
17980
17993
|
return active;
|
|
17981
17994
|
}
|
|
17982
17995
|
function getActiveSessionInfo() {
|
|
17983
17996
|
if (!active)
|
|
17984
17997
|
return null;
|
|
17985
|
-
return
|
|
17986
|
-
deviceId: active.deviceId,
|
|
17987
|
-
platform: active.platform,
|
|
17988
|
-
connectedAt: active.connectedAt,
|
|
17989
|
-
mjpegPort: active.mjpegPort,
|
|
17990
|
-
streamReady: active.streamReady,
|
|
17991
|
-
streamUrl: active.streamUrl
|
|
17992
|
-
};
|
|
17998
|
+
return toInfo(active);
|
|
17993
17999
|
}
|
|
17994
18000
|
function requireActiveSession() {
|
|
17995
18001
|
if (!active) {
|
|
@@ -17997,6 +18003,9 @@ function requireActiveSession() {
|
|
|
17997
18003
|
}
|
|
17998
18004
|
return active;
|
|
17999
18005
|
}
|
|
18006
|
+
function isActiveSessionHeldByRun() {
|
|
18007
|
+
return active?.heldByRunId != null;
|
|
18008
|
+
}
|
|
18000
18009
|
function abandonActiveSession() {
|
|
18001
18010
|
if (!active)
|
|
18002
18011
|
return null;
|
|
@@ -18006,20 +18015,12 @@ function abandonActiveSession() {
|
|
|
18006
18015
|
console.warn(`[yoqa-runner] abandoned dead Appium session for ${info?.platform} ${info?.deviceId}`);
|
|
18007
18016
|
return info;
|
|
18008
18017
|
}
|
|
18009
|
-
|
|
18010
|
-
if (active?.deviceId === deviceId) {
|
|
18011
|
-
abandonActiveSession();
|
|
18012
|
-
}
|
|
18013
|
-
});
|
|
18014
|
-
async function connectDevice(options) {
|
|
18015
|
-
if (active) {
|
|
18016
|
-
await disconnectDevice();
|
|
18017
|
-
}
|
|
18018
|
+
async function createAndRegister(options) {
|
|
18018
18019
|
const session = await createDeviceSession({
|
|
18019
18020
|
platform: options.platform,
|
|
18020
18021
|
deviceId: options.deviceId,
|
|
18021
|
-
appCaps: [],
|
|
18022
|
-
caseCaps: [],
|
|
18022
|
+
appCaps: options.appCaps ?? [],
|
|
18023
|
+
caseCaps: options.caseCaps ?? [],
|
|
18023
18024
|
bundleId: options.bundleId,
|
|
18024
18025
|
appPackage: options.appPackage,
|
|
18025
18026
|
onSessionDead: () => {
|
|
@@ -18033,8 +18034,19 @@ async function connectDevice(options) {
|
|
|
18033
18034
|
mjpegPort: session.mjpegPort,
|
|
18034
18035
|
streamReady: session.streamReady,
|
|
18035
18036
|
streamUrl: "/stream.mjpeg",
|
|
18036
|
-
session
|
|
18037
|
+
session,
|
|
18038
|
+
heldByRunId: options.heldByRunId
|
|
18037
18039
|
};
|
|
18040
|
+
return session;
|
|
18041
|
+
}
|
|
18042
|
+
async function connectDevice(options) {
|
|
18043
|
+
if (isActiveSessionHeldByRun()) {
|
|
18044
|
+
throw new SessionBusyError("A run is using the device session. Cancel the run before connecting another device.");
|
|
18045
|
+
}
|
|
18046
|
+
if (active) {
|
|
18047
|
+
await disconnectDevice();
|
|
18048
|
+
}
|
|
18049
|
+
await createAndRegister({ ...options, heldByRunId: null });
|
|
18038
18050
|
const info = getActiveSessionInfo();
|
|
18039
18051
|
if (!info) {
|
|
18040
18052
|
throw new Error("Failed to read active session after connect");
|
|
@@ -18044,6 +18056,9 @@ async function connectDevice(options) {
|
|
|
18044
18056
|
async function disconnectDevice() {
|
|
18045
18057
|
if (!active)
|
|
18046
18058
|
return null;
|
|
18059
|
+
if (isActiveSessionHeldByRun()) {
|
|
18060
|
+
throw new SessionBusyError("A run is using the device session. Cancel the run first.");
|
|
18061
|
+
}
|
|
18047
18062
|
const info = getActiveSessionInfo();
|
|
18048
18063
|
const session = active.session;
|
|
18049
18064
|
active = null;
|
|
@@ -18053,6 +18068,58 @@ async function disconnectDevice() {
|
|
|
18053
18068
|
} catch {}
|
|
18054
18069
|
return info;
|
|
18055
18070
|
}
|
|
18071
|
+
async function acquireSessionForRun(options) {
|
|
18072
|
+
const current = active;
|
|
18073
|
+
if (current?.heldByRunId && current.heldByRunId !== options.runId) {
|
|
18074
|
+
const session2 = await createDeviceSession({
|
|
18075
|
+
platform: options.platform,
|
|
18076
|
+
deviceId: options.deviceId,
|
|
18077
|
+
appCaps: options.appCaps ?? [],
|
|
18078
|
+
caseCaps: options.caseCaps ?? [],
|
|
18079
|
+
bundleId: options.bundleId,
|
|
18080
|
+
appPackage: options.appPackage,
|
|
18081
|
+
onSessionDead: () => {
|
|
18082
|
+
return;
|
|
18083
|
+
}
|
|
18084
|
+
});
|
|
18085
|
+
return { session: session2, shared: false };
|
|
18086
|
+
}
|
|
18087
|
+
if (current && current.deviceId === options.deviceId) {
|
|
18088
|
+
const healthy = await current.session.getWindowSize().then(() => true).catch(() => false);
|
|
18089
|
+
if (healthy) {
|
|
18090
|
+
current.heldByRunId = options.runId;
|
|
18091
|
+
return { session: current.session, shared: true };
|
|
18092
|
+
}
|
|
18093
|
+
console.warn(`[yoqa-runner] active session for ${options.deviceId} is dead \u2014 creating a fresh one for the run`);
|
|
18094
|
+
await disconnectDevice().catch(() => {
|
|
18095
|
+
return;
|
|
18096
|
+
});
|
|
18097
|
+
}
|
|
18098
|
+
if (current) {
|
|
18099
|
+
await disconnectDevice();
|
|
18100
|
+
}
|
|
18101
|
+
const session = await createAndRegister({
|
|
18102
|
+
deviceId: options.deviceId,
|
|
18103
|
+
platform: options.platform,
|
|
18104
|
+
appCaps: options.appCaps,
|
|
18105
|
+
caseCaps: options.caseCaps,
|
|
18106
|
+
bundleId: options.bundleId,
|
|
18107
|
+
appPackage: options.appPackage,
|
|
18108
|
+
heldByRunId: options.runId
|
|
18109
|
+
});
|
|
18110
|
+
return { session, shared: true };
|
|
18111
|
+
}
|
|
18112
|
+
async function releaseSessionFromRun(runId, session, shared) {
|
|
18113
|
+
if (!shared) {
|
|
18114
|
+
await session.quit().catch(() => {
|
|
18115
|
+
return;
|
|
18116
|
+
});
|
|
18117
|
+
return;
|
|
18118
|
+
}
|
|
18119
|
+
if (active && active.heldByRunId === runId) {
|
|
18120
|
+
active.heldByRunId = null;
|
|
18121
|
+
}
|
|
18122
|
+
}
|
|
18056
18123
|
|
|
18057
18124
|
// src/domains/servers/application.ts
|
|
18058
18125
|
function appiumLabel(info) {
|
|
@@ -24258,7 +24325,7 @@ var probeProviderResponseSchema = exports_external.object({
|
|
|
24258
24325
|
var providerModelSchema = exports_external.object({
|
|
24259
24326
|
id: exports_external.string().min(1),
|
|
24260
24327
|
name: exports_external.string().min(1),
|
|
24261
|
-
|
|
24328
|
+
provider: exports_external.string().min(1).optional()
|
|
24262
24329
|
});
|
|
24263
24330
|
var listProviderModelsResponseSchema = exports_external.object({
|
|
24264
24331
|
models: exports_external.array(providerModelSchema),
|
|
@@ -24351,7 +24418,8 @@ var activeDeviceResponseSchema = exports_external.object({
|
|
|
24351
24418
|
connectedAt: exports_external.number().int().nonnegative(),
|
|
24352
24419
|
mjpegPort: exports_external.number().int().positive().optional(),
|
|
24353
24420
|
streamReady: exports_external.boolean().optional(),
|
|
24354
|
-
streamUrl: exports_external.string().min(1).optional()
|
|
24421
|
+
streamUrl: exports_external.string().min(1).optional(),
|
|
24422
|
+
heldByRun: exports_external.boolean().optional()
|
|
24355
24423
|
});
|
|
24356
24424
|
var controlPointerMessageSchema = exports_external.object({
|
|
24357
24425
|
type: exports_external.literal("pointer"),
|
|
@@ -57545,6 +57613,9 @@ function formatProviderHttpError(label, status, body) {
|
|
|
57545
57613
|
if (label === "OpenCode" && (body.includes("unknown variant `image_url`") || body.includes("No endpoints found that support image input") || body.includes("expected `text`"))) {
|
|
57546
57614
|
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
57615
|
}
|
|
57616
|
+
if (label === "OpenCode" && (body.includes("output_config.format") || body.includes("BedrockException") && body.includes("Extra inputs are not permitted"))) {
|
|
57617
|
+
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.";
|
|
57618
|
+
}
|
|
57548
57619
|
if (label === "OpenCode" && status >= 500 && (body.includes("Internal server error") || body.includes('"type":"error"'))) {
|
|
57549
57620
|
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
57621
|
}
|
|
@@ -57578,6 +57649,16 @@ function mapProviderError(label, error40) {
|
|
|
57578
57649
|
}
|
|
57579
57650
|
throw new AgentProviderError(String(error40));
|
|
57580
57651
|
}
|
|
57652
|
+
function repairModelJsonText(text3) {
|
|
57653
|
+
const trimmed = text3.trim();
|
|
57654
|
+
const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/);
|
|
57655
|
+
const candidate = fenced?.[1]?.trim() ?? trimmed;
|
|
57656
|
+
const start = candidate.indexOf("{");
|
|
57657
|
+
const end = candidate.lastIndexOf("}");
|
|
57658
|
+
if (start < 0 || end <= start)
|
|
57659
|
+
return trimmed;
|
|
57660
|
+
return candidate.slice(start, end + 1);
|
|
57661
|
+
}
|
|
57581
57662
|
async function completeWithAiSdk(input) {
|
|
57582
57663
|
const run = async (prompt) => {
|
|
57583
57664
|
try {
|
|
@@ -57586,6 +57667,7 @@ async function completeWithAiSdk(input) {
|
|
|
57586
57667
|
schema: input.schema,
|
|
57587
57668
|
system: input.system,
|
|
57588
57669
|
maxOutputTokens: VISION_MAX_TOKENS,
|
|
57670
|
+
experimental_repairText: async ({ text: text3 }) => repairModelJsonText(text3),
|
|
57589
57671
|
messages: [
|
|
57590
57672
|
{
|
|
57591
57673
|
role: "user",
|
|
@@ -57602,6 +57684,11 @@ async function completeWithAiSdk(input) {
|
|
|
57602
57684
|
});
|
|
57603
57685
|
return object3;
|
|
57604
57686
|
} catch (error40) {
|
|
57687
|
+
if (NoObjectGeneratedError.isInstance(error40) && error40.text?.trim()) {
|
|
57688
|
+
try {
|
|
57689
|
+
return input.schema.parse(JSON.parse(repairModelJsonText(error40.text)));
|
|
57690
|
+
} catch {}
|
|
57691
|
+
}
|
|
57605
57692
|
mapProviderError(input.label, error40);
|
|
57606
57693
|
}
|
|
57607
57694
|
};
|
|
@@ -57644,41 +57731,58 @@ function openCodeAuthPaths() {
|
|
|
57644
57731
|
paths.push(join9(homedir8(), ".local", "share", "opencode", "auth.json"));
|
|
57645
57732
|
return paths;
|
|
57646
57733
|
}
|
|
57647
|
-
|
|
57734
|
+
function apiKeyFromOpenCodeAuthEntry(value) {
|
|
57735
|
+
if (!value || typeof value !== "object")
|
|
57736
|
+
return null;
|
|
57737
|
+
const entry = value;
|
|
57738
|
+
for (const field of ["key", "apiKey", "token"]) {
|
|
57739
|
+
const candidate = entry[field];
|
|
57740
|
+
if (typeof candidate === "string" && candidate.trim())
|
|
57741
|
+
return candidate.trim();
|
|
57742
|
+
}
|
|
57743
|
+
return null;
|
|
57744
|
+
}
|
|
57745
|
+
async function readOpenCodeCliAuthJson() {
|
|
57648
57746
|
for (const path of openCodeAuthPaths()) {
|
|
57649
57747
|
try {
|
|
57650
57748
|
const file2 = Bun.file(path);
|
|
57651
57749
|
if (!await file2.exists())
|
|
57652
57750
|
continue;
|
|
57653
57751
|
const json3 = await file2.json();
|
|
57654
|
-
if (!json3 || typeof json3 !== "object")
|
|
57752
|
+
if (!json3 || typeof json3 !== "object" || Array.isArray(json3))
|
|
57655
57753
|
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
|
-
}
|
|
57754
|
+
return json3;
|
|
57678
57755
|
} catch {}
|
|
57679
57756
|
}
|
|
57680
57757
|
return null;
|
|
57681
57758
|
}
|
|
57759
|
+
async function readOpenCodeCliAuthKeyForProvider(providerId) {
|
|
57760
|
+
const record2 = await readOpenCodeCliAuthJson();
|
|
57761
|
+
if (!record2)
|
|
57762
|
+
return null;
|
|
57763
|
+
return apiKeyFromOpenCodeAuthEntry(record2[providerId]);
|
|
57764
|
+
}
|
|
57765
|
+
async function readOpenCodeCliAuthKey() {
|
|
57766
|
+
const record2 = await readOpenCodeCliAuthJson();
|
|
57767
|
+
if (!record2)
|
|
57768
|
+
return null;
|
|
57769
|
+
const zen = apiKeyFromOpenCodeAuthEntry(record2.opencode);
|
|
57770
|
+
if (zen)
|
|
57771
|
+
return zen;
|
|
57772
|
+
for (const value of Object.values(record2)) {
|
|
57773
|
+
const key = apiKeyFromOpenCodeAuthEntry(value);
|
|
57774
|
+
if (!key)
|
|
57775
|
+
continue;
|
|
57776
|
+
if (!value || typeof value !== "object")
|
|
57777
|
+
continue;
|
|
57778
|
+
const entry = value;
|
|
57779
|
+
const type = typeof entry.type === "string" ? entry.type.toLowerCase() : "";
|
|
57780
|
+
if (!type || type.includes("api") || type.includes("key") || type === "opencode") {
|
|
57781
|
+
return key;
|
|
57782
|
+
}
|
|
57783
|
+
}
|
|
57784
|
+
return null;
|
|
57785
|
+
}
|
|
57682
57786
|
async function resolveOpenAiCompatibleKey(auth) {
|
|
57683
57787
|
if (auth.apiKey?.trim())
|
|
57684
57788
|
return auth.apiKey.trim();
|
|
@@ -57721,18 +57825,34 @@ function withOpenCodeRequestHooks(opts) {
|
|
|
57721
57825
|
const fetchImpl = opts.fetchImpl ?? globalThis.fetch;
|
|
57722
57826
|
return async (input, init) => {
|
|
57723
57827
|
const headers = new Headers(init?.headers);
|
|
57724
|
-
if (opts.authHeaders
|
|
57725
|
-
|
|
57828
|
+
if (opts.authHeaders) {
|
|
57829
|
+
for (const [key, value] of Object.entries(opts.authHeaders)) {
|
|
57830
|
+
if (value)
|
|
57831
|
+
headers.set(key, value);
|
|
57832
|
+
}
|
|
57833
|
+
}
|
|
57834
|
+
if (opts.extraHeaders) {
|
|
57835
|
+
for (const [key, value] of Object.entries(opts.extraHeaders)) {
|
|
57836
|
+
if (value)
|
|
57837
|
+
headers.set(key, value);
|
|
57838
|
+
}
|
|
57726
57839
|
}
|
|
57727
|
-
|
|
57840
|
+
const rawBody = init?.body;
|
|
57841
|
+
const shouldRewriteBody = (opts.disableThinking || Boolean(opts.stripResponseFormat)) && typeof rawBody === "string";
|
|
57842
|
+
if (!shouldRewriteBody || typeof rawBody !== "string") {
|
|
57728
57843
|
return fetchImpl(input, { ...init, headers });
|
|
57729
57844
|
}
|
|
57730
57845
|
try {
|
|
57731
|
-
const parsed = JSON.parse(
|
|
57846
|
+
const parsed = JSON.parse(rawBody);
|
|
57732
57847
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
57733
57848
|
return fetchImpl(input, { ...init, headers });
|
|
57734
57849
|
}
|
|
57735
|
-
const
|
|
57850
|
+
const parsedBody = { ...parsed };
|
|
57851
|
+
if (opts.disableThinking) {
|
|
57852
|
+
parsedBody.thinking = { type: "disabled" };
|
|
57853
|
+
}
|
|
57854
|
+
const { response_format: _responseFormat, ...withoutFormat } = parsedBody;
|
|
57855
|
+
const body = opts.stripResponseFormat ? withoutFormat : parsedBody;
|
|
57736
57856
|
return fetchImpl(input, {
|
|
57737
57857
|
...init,
|
|
57738
57858
|
headers,
|
|
@@ -96236,12 +96356,144 @@ var openaiDriver = {
|
|
|
96236
96356
|
}
|
|
96237
96357
|
};
|
|
96238
96358
|
|
|
96359
|
+
// src/domains/providers/drivers/opencode-config.ts
|
|
96360
|
+
import { homedir as homedir10 } from "os";
|
|
96361
|
+
import { join as join13 } from "path";
|
|
96362
|
+
var OPENAI_COMPATIBLE_PACKAGES = new Set([
|
|
96363
|
+
"@ai-sdk/openai-compatible",
|
|
96364
|
+
"@ai-sdk/openai",
|
|
96365
|
+
"@ai-sdk/azure"
|
|
96366
|
+
]);
|
|
96367
|
+
function asRecord3(value) {
|
|
96368
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
96369
|
+
return null;
|
|
96370
|
+
return value;
|
|
96371
|
+
}
|
|
96372
|
+
function isOpenAiCompatibleNpm(npm) {
|
|
96373
|
+
const trimmed = npm.trim();
|
|
96374
|
+
if (!trimmed)
|
|
96375
|
+
return true;
|
|
96376
|
+
if (OPENAI_COMPATIBLE_PACKAGES.has(trimmed))
|
|
96377
|
+
return true;
|
|
96378
|
+
return trimmed.includes("openai-compatible");
|
|
96379
|
+
}
|
|
96380
|
+
function interpolateOpenCodeTemplate(value, env) {
|
|
96381
|
+
return value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}/g, (_full, name31) => {
|
|
96382
|
+
return env[name31]?.trim() || process.env[name31]?.trim() || "";
|
|
96383
|
+
}).trim();
|
|
96384
|
+
}
|
|
96385
|
+
function openCodeConfigPaths() {
|
|
96386
|
+
const paths = [];
|
|
96387
|
+
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
96388
|
+
if (xdg)
|
|
96389
|
+
paths.push(join13(xdg, "opencode", "opencode.json"));
|
|
96390
|
+
paths.push(join13(homedir10(), ".config", "opencode", "opencode.json"));
|
|
96391
|
+
return paths;
|
|
96392
|
+
}
|
|
96393
|
+
function parseOpenCodeCompatibleProvider(config2, providerId) {
|
|
96394
|
+
const root = asRecord3(config2);
|
|
96395
|
+
const providers2 = asRecord3(root?.provider);
|
|
96396
|
+
const entry = asRecord3(providers2?.[providerId]);
|
|
96397
|
+
if (!entry)
|
|
96398
|
+
return null;
|
|
96399
|
+
const npm = typeof entry.npm === "string" ? entry.npm : "";
|
|
96400
|
+
if (!isOpenAiCompatibleNpm(npm))
|
|
96401
|
+
return null;
|
|
96402
|
+
const options = asRecord3(entry.options);
|
|
96403
|
+
const baseURLRaw = typeof options?.baseURL === "string" ? options.baseURL.trim() : "";
|
|
96404
|
+
if (!baseURLRaw)
|
|
96405
|
+
return null;
|
|
96406
|
+
const apiKeyTemplate = typeof options?.apiKey === "string" ? options.apiKey : null;
|
|
96407
|
+
const headers = {};
|
|
96408
|
+
const headerBag = asRecord3(options?.headers);
|
|
96409
|
+
if (headerBag) {
|
|
96410
|
+
for (const [key, value] of Object.entries(headerBag)) {
|
|
96411
|
+
if (typeof value === "string" && value.trim())
|
|
96412
|
+
headers[key] = value;
|
|
96413
|
+
}
|
|
96414
|
+
}
|
|
96415
|
+
return {
|
|
96416
|
+
providerId,
|
|
96417
|
+
baseURL: baseURLRaw.replace(/\/$/, ""),
|
|
96418
|
+
apiKeyTemplate,
|
|
96419
|
+
headers
|
|
96420
|
+
};
|
|
96421
|
+
}
|
|
96422
|
+
function resolveOpenCodeCompatibleAuth(input) {
|
|
96423
|
+
const parsed = parseOpenCodeCompatibleProvider(input.config, input.providerId);
|
|
96424
|
+
const override = input.overrideBaseUrl?.trim().replace(/\/$/, "") || null;
|
|
96425
|
+
if (!parsed && !(override && input.providerId === "litellm"))
|
|
96426
|
+
return null;
|
|
96427
|
+
const baseURL = override || parsed?.baseURL || null;
|
|
96428
|
+
if (!baseURL)
|
|
96429
|
+
return null;
|
|
96430
|
+
const headers = {};
|
|
96431
|
+
if (parsed) {
|
|
96432
|
+
for (const [key, value] of Object.entries(parsed.headers)) {
|
|
96433
|
+
const interpolated = interpolateOpenCodeTemplate(value, input.env);
|
|
96434
|
+
if (interpolated)
|
|
96435
|
+
headers[key] = interpolated;
|
|
96436
|
+
}
|
|
96437
|
+
}
|
|
96438
|
+
const fromTemplate = parsed?.apiKeyTemplate ? interpolateOpenCodeTemplate(parsed.apiKeyTemplate, input.env) : "";
|
|
96439
|
+
const envKeyName = `${input.providerId.replace(/-/g, "_").toUpperCase()}_API_KEY`;
|
|
96440
|
+
const apiKey = fromTemplate || input.env[envKeyName]?.trim() || process.env[envKeyName]?.trim() || input.storedKey?.trim() || null;
|
|
96441
|
+
return { baseURL, apiKey, headers };
|
|
96442
|
+
}
|
|
96443
|
+
async function loadOpenCodeUserConfig() {
|
|
96444
|
+
for (const path of openCodeConfigPaths()) {
|
|
96445
|
+
try {
|
|
96446
|
+
const file2 = Bun.file(path);
|
|
96447
|
+
if (!await file2.exists())
|
|
96448
|
+
continue;
|
|
96449
|
+
return await file2.json();
|
|
96450
|
+
} catch {}
|
|
96451
|
+
}
|
|
96452
|
+
return null;
|
|
96453
|
+
}
|
|
96454
|
+
|
|
96239
96455
|
// src/domains/providers/drivers/opencode.ts
|
|
96240
|
-
var OPENCODE_FREE_MODEL_IDS = new Set(["big-pickle"]);
|
|
96241
96456
|
var OPENCODE_DEFAULT_VISION_MODEL = "mimo-v2.5-free";
|
|
96242
|
-
|
|
96243
|
-
|
|
96244
|
-
|
|
96457
|
+
var OPENCODE_PROVIDER_LABELS = {
|
|
96458
|
+
"amazon-bedrock": "Amazon Bedrock",
|
|
96459
|
+
anthropic: "Anthropic",
|
|
96460
|
+
azure: "Azure OpenAI",
|
|
96461
|
+
"github-copilot": "GitHub Copilot",
|
|
96462
|
+
google: "Google",
|
|
96463
|
+
"google-vertex": "Google Vertex AI",
|
|
96464
|
+
groq: "Groq",
|
|
96465
|
+
litellm: "LiteLLM",
|
|
96466
|
+
ollama: "Ollama",
|
|
96467
|
+
openai: "OpenAI",
|
|
96468
|
+
opencode: "OpenCode Zen",
|
|
96469
|
+
"opencode-go": "OpenCode Go",
|
|
96470
|
+
openrouter: "OpenRouter",
|
|
96471
|
+
xai: "xAI",
|
|
96472
|
+
zenmux: "ZenMux"
|
|
96473
|
+
};
|
|
96474
|
+
function humanizeOpenCodeProvider(providerId) {
|
|
96475
|
+
const known = OPENCODE_PROVIDER_LABELS[providerId];
|
|
96476
|
+
if (known)
|
|
96477
|
+
return known;
|
|
96478
|
+
return providerId.split(/[-_]/).filter(Boolean).map((part) => {
|
|
96479
|
+
if (part.toLowerCase() === "ai")
|
|
96480
|
+
return "AI";
|
|
96481
|
+
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
96482
|
+
}).join(" ");
|
|
96483
|
+
}
|
|
96484
|
+
function parseOpenCodeModelSlug(slug) {
|
|
96485
|
+
const trimmed = slug.trim();
|
|
96486
|
+
const sep = trimmed.indexOf("/");
|
|
96487
|
+
if (sep <= 0 || sep === trimmed.length - 1) {
|
|
96488
|
+
return { providerId: "opencode", modelId: trimmed };
|
|
96489
|
+
}
|
|
96490
|
+
return {
|
|
96491
|
+
providerId: trimmed.slice(0, sep),
|
|
96492
|
+
modelId: trimmed.slice(sep + 1)
|
|
96493
|
+
};
|
|
96494
|
+
}
|
|
96495
|
+
function isOpenCodeZenModel(slug) {
|
|
96496
|
+
return parseOpenCodeModelSlug(slug).providerId === "opencode";
|
|
96245
96497
|
}
|
|
96246
96498
|
function humanizeModelId(id) {
|
|
96247
96499
|
return id.split(/[-_]/).filter(Boolean).map((part) => {
|
|
@@ -96252,11 +96504,13 @@ function humanizeModelId(id) {
|
|
|
96252
96504
|
return part.charAt(0).toUpperCase() + part.slice(1);
|
|
96253
96505
|
}).join(" ");
|
|
96254
96506
|
}
|
|
96255
|
-
function toOpenCodeModelEntry(
|
|
96507
|
+
function toOpenCodeModelEntry(slug) {
|
|
96508
|
+
const trimmed = slug.trim();
|
|
96509
|
+
const { providerId, modelId } = parseOpenCodeModelSlug(trimmed);
|
|
96256
96510
|
return {
|
|
96257
|
-
id,
|
|
96258
|
-
name: humanizeModelId(
|
|
96259
|
-
|
|
96511
|
+
id: trimmed,
|
|
96512
|
+
name: humanizeModelId(modelId),
|
|
96513
|
+
provider: humanizeOpenCodeProvider(providerId)
|
|
96260
96514
|
};
|
|
96261
96515
|
}
|
|
96262
96516
|
function openCodeServerAuthHeaders(password) {
|
|
@@ -96270,18 +96524,14 @@ function resolveOpenCodeServerPassword(env) {
|
|
|
96270
96524
|
return env.OPENCODE_SERVER_PASSWORD?.trim() || env.SERVER_PASSWORD?.trim() || process.env.OPENCODE_SERVER_PASSWORD?.trim() || process.env.SERVER_PASSWORD?.trim() || null;
|
|
96271
96525
|
}
|
|
96272
96526
|
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);
|
|
96527
|
+
return parseOpenCodeModelSlug(slug).modelId;
|
|
96278
96528
|
}
|
|
96279
96529
|
function parseModelsPayload(json3) {
|
|
96280
96530
|
const models = [];
|
|
96281
96531
|
if (json3 && typeof json3 === "object" && "data" in json3 && Array.isArray(json3.data)) {
|
|
96282
96532
|
for (const item of json3.data) {
|
|
96283
96533
|
if (item && typeof item === "object" && "id" in item && typeof item.id === "string") {
|
|
96284
|
-
models.push(toOpenCodeModelEntry(
|
|
96534
|
+
models.push(toOpenCodeModelEntry(item.id));
|
|
96285
96535
|
}
|
|
96286
96536
|
}
|
|
96287
96537
|
}
|
|
@@ -96310,44 +96560,84 @@ async function listOpenCodeModelsFromCli(binaryPath) {
|
|
|
96310
96560
|
const slug = line.trim();
|
|
96311
96561
|
if (!slug || /\s/.test(slug))
|
|
96312
96562
|
continue;
|
|
96313
|
-
|
|
96314
|
-
if (!id || seen.has(id))
|
|
96563
|
+
if (seen.has(slug))
|
|
96315
96564
|
continue;
|
|
96316
|
-
seen.add(
|
|
96317
|
-
models.push(toOpenCodeModelEntry(
|
|
96565
|
+
seen.add(slug);
|
|
96566
|
+
models.push(toOpenCodeModelEntry(slug));
|
|
96318
96567
|
}
|
|
96319
96568
|
return {
|
|
96320
96569
|
models,
|
|
96321
96570
|
detail: `${models.length} models from opencode CLI`
|
|
96322
96571
|
};
|
|
96323
96572
|
}
|
|
96573
|
+
async function resolveCompatibleOpenCodeVision(providerId, auth) {
|
|
96574
|
+
const config2 = await loadOpenCodeUserConfig();
|
|
96575
|
+
const storedKey = await readOpenCodeCliAuthKeyForProvider(providerId);
|
|
96576
|
+
return resolveOpenCodeCompatibleAuth({
|
|
96577
|
+
providerId,
|
|
96578
|
+
config: config2,
|
|
96579
|
+
env: auth.env,
|
|
96580
|
+
storedKey,
|
|
96581
|
+
overrideBaseUrl: auth.baseUrl
|
|
96582
|
+
});
|
|
96583
|
+
}
|
|
96324
96584
|
var opencodeDriver = {
|
|
96325
96585
|
kind: "opencode",
|
|
96326
96586
|
label: "OpenCode",
|
|
96327
|
-
description: "OpenCode Zen (OpenAI-compatible). Paste a Zen API key for
|
|
96587
|
+
description: "OpenCode Zen or LiteLLM (OpenAI-compatible). Paste a Zen API key for Zen; LiteLLM uses opencode.json.",
|
|
96328
96588
|
defaultBinary: "opencode",
|
|
96329
96589
|
authModes: ["cli", "api_key"],
|
|
96330
|
-
envHints: [
|
|
96331
|
-
|
|
96590
|
+
envHints: [
|
|
96591
|
+
"OPENCODE_API_KEY",
|
|
96592
|
+
"OPENCODE_ZEN_API_KEY",
|
|
96593
|
+
"OPENCODE_SERVER_PASSWORD",
|
|
96594
|
+
"LITELLM_API_KEY"
|
|
96595
|
+
],
|
|
96596
|
+
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
96597
|
capabilities: { vision: true },
|
|
96333
96598
|
vision: createSdkVisionPort({
|
|
96334
96599
|
label: "OpenCode",
|
|
96335
96600
|
defaultModel: OPENCODE_DEFAULT_VISION_MODEL,
|
|
96336
96601
|
createModel: async (auth, modelId) => {
|
|
96337
|
-
|
|
96338
|
-
|
|
96339
|
-
|
|
96602
|
+
if (isOpenCodeZenModel(modelId)) {
|
|
96603
|
+
const apiKey = await resolveOpenAiCompatibleKey(auth);
|
|
96604
|
+
if (!apiKey) {
|
|
96605
|
+
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.");
|
|
96606
|
+
}
|
|
96607
|
+
const baseURL = auth.baseUrl?.trim().replace(/\/$/, "") || "https://opencode.ai/zen/v1";
|
|
96608
|
+
return createOpenAI({
|
|
96609
|
+
apiKey,
|
|
96610
|
+
baseURL,
|
|
96611
|
+
name: "opencode",
|
|
96612
|
+
fetch: withOpenCodeRequestHooks({
|
|
96613
|
+
disableThinking: true,
|
|
96614
|
+
authHeaders: null
|
|
96615
|
+
})
|
|
96616
|
+
}).chat(stripOpenCodeModelSlug(modelId));
|
|
96617
|
+
}
|
|
96618
|
+
const { providerId } = parseOpenCodeModelSlug(modelId);
|
|
96619
|
+
const providerLabel = humanizeOpenCodeProvider(providerId);
|
|
96620
|
+
const compatible = await resolveCompatibleOpenCodeVision(providerId, auth);
|
|
96621
|
+
if (compatible?.apiKey) {
|
|
96622
|
+
return createOpenAI({
|
|
96623
|
+
apiKey: compatible.apiKey,
|
|
96624
|
+
baseURL: compatible.baseURL,
|
|
96625
|
+
name: providerId,
|
|
96626
|
+
fetch: withOpenCodeRequestHooks({
|
|
96627
|
+
disableThinking: false,
|
|
96628
|
+
stripResponseFormat: true,
|
|
96629
|
+
authHeaders: null,
|
|
96630
|
+
extraHeaders: Object.keys(compatible.headers).length > 0 ? compatible.headers : null
|
|
96631
|
+
})
|
|
96632
|
+
}).chat(stripOpenCodeModelSlug(modelId));
|
|
96340
96633
|
}
|
|
96341
|
-
|
|
96342
|
-
|
|
96343
|
-
|
|
96344
|
-
|
|
96345
|
-
|
|
96346
|
-
|
|
96347
|
-
|
|
96348
|
-
authHeaders: null
|
|
96349
|
-
})
|
|
96350
|
-
}).chat(modelId);
|
|
96634
|
+
if (compatible && !compatible.apiKey) {
|
|
96635
|
+
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.`);
|
|
96636
|
+
}
|
|
96637
|
+
if (providerId === "litellm") {
|
|
96638
|
+
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.");
|
|
96639
|
+
}
|
|
96640
|
+
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
96641
|
}
|
|
96352
96642
|
}),
|
|
96353
96643
|
async probe(binaryPath) {
|
|
@@ -96438,6 +96728,10 @@ var opencodeDriver = {
|
|
|
96438
96728
|
async listModels(input) {
|
|
96439
96729
|
const key = resolveOpenCodeKey(input);
|
|
96440
96730
|
const serverUrl = input.serverUrl?.trim() || null;
|
|
96731
|
+
const cli = await listOpenCodeModelsFromCli(input.binaryPath);
|
|
96732
|
+
if (cli.models.length > 0) {
|
|
96733
|
+
return { models: cli.models, message: cli.detail };
|
|
96734
|
+
}
|
|
96441
96735
|
if (serverUrl) {
|
|
96442
96736
|
try {
|
|
96443
96737
|
const password = resolveOpenCodeServerPassword(input.env);
|
|
@@ -96463,14 +96757,10 @@ var opencodeDriver = {
|
|
|
96463
96757
|
label: "OpenCode"
|
|
96464
96758
|
});
|
|
96465
96759
|
return {
|
|
96466
|
-
models: (result.models ?? []).map((id) => toOpenCodeModelEntry(
|
|
96760
|
+
models: (result.models ?? []).map((id) => toOpenCodeModelEntry(id)),
|
|
96467
96761
|
message: result.ok ? `${result.models?.length ?? 0} models available` : result.message
|
|
96468
96762
|
};
|
|
96469
96763
|
}
|
|
96470
|
-
const cli = await listOpenCodeModelsFromCli(input.binaryPath);
|
|
96471
|
-
if (cli.models.length > 0) {
|
|
96472
|
-
return { models: cli.models, message: cli.detail };
|
|
96473
|
-
}
|
|
96474
96764
|
return {
|
|
96475
96765
|
models: [],
|
|
96476
96766
|
message: "Add OPENCODE_API_KEY, a server URL, or install the opencode CLI to list models"
|
|
@@ -96543,10 +96833,10 @@ import {
|
|
|
96543
96833
|
randomBytes
|
|
96544
96834
|
} from "crypto";
|
|
96545
96835
|
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 =
|
|
96836
|
+
import { homedir as homedir11 } from "os";
|
|
96837
|
+
import { dirname as dirname3, join as join14 } from "path";
|
|
96838
|
+
var YOQA_ROOT5 = join14(homedir11(), ".yoqa");
|
|
96839
|
+
var KEY_PATH = join14(YOQA_ROOT5, ".provider-key");
|
|
96550
96840
|
var ALGORITHM = "aes-256-gcm";
|
|
96551
96841
|
var IV_LENGTH = 12;
|
|
96552
96842
|
var KEY_LENGTH = 32;
|
|
@@ -98107,6 +98397,7 @@ async function executeRun(runId) {
|
|
|
98107
98397
|
return;
|
|
98108
98398
|
registerControl(runId);
|
|
98109
98399
|
let session = null;
|
|
98400
|
+
let sharedSession = true;
|
|
98110
98401
|
try {
|
|
98111
98402
|
if (isAborted2(runId)) {
|
|
98112
98403
|
await persistCancelled(runId);
|
|
@@ -98147,14 +98438,17 @@ async function executeRun(runId) {
|
|
|
98147
98438
|
}
|
|
98148
98439
|
}
|
|
98149
98440
|
const firstCase = run.tests[0] ? await getCase(run.tests[0].caseId) : null;
|
|
98150
|
-
|
|
98151
|
-
|
|
98441
|
+
const acquired = await acquireSessionForRun({
|
|
98442
|
+
runId,
|
|
98152
98443
|
deviceId: run.deviceId,
|
|
98444
|
+
platform: run.platform,
|
|
98153
98445
|
appCaps: app.capabilities,
|
|
98154
98446
|
caseCaps: firstCase?.capabilities ?? [],
|
|
98155
98447
|
bundleId: app.iosBundleId || undefined,
|
|
98156
98448
|
appPackage: app.androidApplicationId || undefined
|
|
98157
98449
|
});
|
|
98450
|
+
session = acquired.session;
|
|
98451
|
+
sharedSession = acquired.shared;
|
|
98158
98452
|
if (isAborted2(runId)) {
|
|
98159
98453
|
await persistCancelled(runId);
|
|
98160
98454
|
return;
|
|
@@ -98229,14 +98523,15 @@ async function executeRun(runId) {
|
|
|
98229
98523
|
}
|
|
98230
98524
|
} finally {
|
|
98231
98525
|
if (session) {
|
|
98232
|
-
await
|
|
98233
|
-
return;
|
|
98234
|
-
});
|
|
98526
|
+
await releaseSessionFromRun(runId, session, sharedSession);
|
|
98235
98527
|
}
|
|
98236
98528
|
clearControl(runId);
|
|
98237
98529
|
}
|
|
98238
98530
|
}
|
|
98239
98531
|
async function createRun(input) {
|
|
98532
|
+
if (runControls.size > 0) {
|
|
98533
|
+
throw new RunValidationError("A run is already in progress. Cancel it before starting another.");
|
|
98534
|
+
}
|
|
98240
98535
|
const uniqueCaseIds = [...new Set(input.caseIds)];
|
|
98241
98536
|
if (uniqueCaseIds.length === 0) {
|
|
98242
98537
|
throw new RunValidationError("At least one case id is required");
|
|
@@ -98594,6 +98889,12 @@ import { mkdir as mkdir4, rename as rename2 } from "fs/promises";
|
|
|
98594
98889
|
import { dirname as dirname4 } from "path";
|
|
98595
98890
|
function sessionErrorResponse(error40) {
|
|
98596
98891
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98892
|
+
if (error40 instanceof SessionBusyError) {
|
|
98893
|
+
return {
|
|
98894
|
+
status: 409,
|
|
98895
|
+
body: { error: error40.message }
|
|
98896
|
+
};
|
|
98897
|
+
}
|
|
98597
98898
|
if (isMissingAppiumSessionError(error40)) {
|
|
98598
98899
|
abandonActiveSession();
|
|
98599
98900
|
return {
|
|
@@ -98623,6 +98924,9 @@ function createSessionRoutes() {
|
|
|
98623
98924
|
const info = await connectDevice(parsed.data);
|
|
98624
98925
|
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98625
98926
|
} catch (error40) {
|
|
98927
|
+
const mapped = sessionErrorResponse(error40);
|
|
98928
|
+
if (mapped)
|
|
98929
|
+
return c.json(mapped.body, mapped.status);
|
|
98626
98930
|
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98627
98931
|
return c.json({ error: "Failed to connect device", detail: message }, 500);
|
|
98628
98932
|
}
|
|
@@ -98635,11 +98939,19 @@ function createSessionRoutes() {
|
|
|
98635
98939
|
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98636
98940
|
});
|
|
98637
98941
|
app.post("/devices/disconnect", async (c) => {
|
|
98638
|
-
|
|
98639
|
-
|
|
98640
|
-
|
|
98942
|
+
try {
|
|
98943
|
+
const info = await disconnectDevice();
|
|
98944
|
+
if (!info) {
|
|
98945
|
+
return c.json({ error: "No active device session" }, 404);
|
|
98946
|
+
}
|
|
98947
|
+
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98948
|
+
} catch (error40) {
|
|
98949
|
+
const mapped = sessionErrorResponse(error40);
|
|
98950
|
+
if (mapped)
|
|
98951
|
+
return c.json(mapped.body, mapped.status);
|
|
98952
|
+
const message = error40 instanceof Error ? error40.message : String(error40);
|
|
98953
|
+
return c.json({ error: "Failed to disconnect device", detail: message }, 500);
|
|
98641
98954
|
}
|
|
98642
|
-
return c.json(activeDeviceResponseSchema.parse(info));
|
|
98643
98955
|
});
|
|
98644
98956
|
app.get("/screen", async (c) => {
|
|
98645
98957
|
try {
|
|
@@ -98760,6 +99072,11 @@ function createSessionRoutes() {
|
|
|
98760
99072
|
}
|
|
98761
99073
|
try {
|
|
98762
99074
|
const { session } = requireActiveSession();
|
|
99075
|
+
if (isActiveSessionHeldByRun()) {
|
|
99076
|
+
return c.json({
|
|
99077
|
+
error: "A run is using this device session. Cancel the run to interact manually."
|
|
99078
|
+
}, 409);
|
|
99079
|
+
}
|
|
98763
99080
|
const result = await performAction(session, parsed.data);
|
|
98764
99081
|
return c.json(actionResponseSchema.parse(result));
|
|
98765
99082
|
} catch (error40) {
|
|
@@ -98888,6 +99205,13 @@ var controlWebSocket = {
|
|
|
98888
99205
|
return;
|
|
98889
99206
|
}
|
|
98890
99207
|
const msg = result.data;
|
|
99208
|
+
if (isActiveSessionHeldByRun()) {
|
|
99209
|
+
ws.send(JSON.stringify({
|
|
99210
|
+
ok: false,
|
|
99211
|
+
error: "A run is using this device session. Cancel the run to interact manually."
|
|
99212
|
+
}));
|
|
99213
|
+
return;
|
|
99214
|
+
}
|
|
98891
99215
|
try {
|
|
98892
99216
|
await active2.session.pointerEvent(msg.phase, msg.x, msg.y, msg.seq);
|
|
98893
99217
|
if (msg.phase === "end" || msg.phase === "begin") {
|
package/package.json
CHANGED