diff --git a/src/client.ts b/src/client.ts index b3e487a..25e117e 100644 --- a/src/client.ts +++ b/src/client.ts @@ -13,6 +13,7 @@ import { TeamService } from "./services/team"; import { ComputerActionService } from "./services/computer-action"; import { GeminiComputerUseService } from "./services/agents/gemini-computer-use"; import { GrokComputerUseService } from "./services/agents/grok-computer-use"; +import { MetaComputerUseService } from "./services/agents/meta-computer-use"; import { WebService } from "./services/web"; import { SandboxesService } from "./services/sandboxes"; import { VolumesService } from "./services/volumes"; @@ -69,6 +70,7 @@ export class HyperbrowserClient { hyperAgent: HyperAgentService; geminiComputerUse: GeminiComputerUseService; grokComputerUse: GrokComputerUseService; + metaComputerUse: MetaComputerUseService; }; public readonly team: TeamService; public readonly computerAction: ComputerActionService; @@ -105,6 +107,7 @@ export class HyperbrowserClient { hyperAgent: new HyperAgentService(apiKey, baseUrl, timeout), geminiComputerUse: new GeminiComputerUseService(apiKey, baseUrl, timeout), grokComputerUse: new GrokComputerUseService(apiKey, baseUrl, timeout), + metaComputerUse: new MetaComputerUseService(apiKey, baseUrl, timeout), }; } } diff --git a/src/services/agents/meta-computer-use.ts b/src/services/agents/meta-computer-use.ts new file mode 100644 index 0000000..d37e703 --- /dev/null +++ b/src/services/agents/meta-computer-use.ts @@ -0,0 +1,116 @@ +import { HyperbrowserError } from "../../client"; +import { BasicResponse } from "../../types"; +import { POLLING_ATTEMPTS } from "../../types/constants"; +import { sleep } from "../../utils"; +import { BaseService } from "../base"; +import { + MetaComputerUseTaskResponse, + MetaComputerUseTaskStatusResponse, + StartMetaComputerUseTaskParams, + StartMetaComputerUseTaskResponse, +} from "../../types/agents/meta-computer-use"; + +export class MetaComputerUseService extends BaseService { + /** + * Start a new Meta Computer Use task job + * @param params The parameters for the task job + */ + async start(params: StartMetaComputerUseTaskParams): Promise { + try { + return await this.request("/task/meta-computer-use", { + method: "POST", + body: JSON.stringify(params), + }); + } catch (error) { + if (error instanceof HyperbrowserError) { + throw error; + } + throw new HyperbrowserError("Failed to start Meta Computer Use task job", undefined); + } + } + + /** + * Get the status of a Meta Computer Use task job + * @param id The ID of the task job to get + */ + async getStatus(id: string): Promise { + try { + return await this.request( + `/task/meta-computer-use/${id}/status` + ); + } catch (error) { + if (error instanceof HyperbrowserError) { + throw error; + } + throw new HyperbrowserError( + `Failed to get Meta Computer Use task job ${id} status`, + undefined + ); + } + } + + /** + * Get the result of a Meta Computer Use task job + * @param id The ID of the task job to get + */ + async get(id: string): Promise { + try { + return await this.request(`/task/meta-computer-use/${id}`); + } catch (error) { + if (error instanceof HyperbrowserError) { + throw error; + } + throw new HyperbrowserError(`Failed to get Meta Computer Use task job ${id}`, undefined); + } + } + + /** + * Stop a Meta Computer Use task job + * @param id The ID of the task job to stop + */ + async stop(id: string): Promise { + try { + return await this.request(`/task/meta-computer-use/${id}/stop`, { + method: "PUT", + }); + } catch (error) { + if (error instanceof HyperbrowserError) { + throw error; + } + throw new HyperbrowserError(`Failed to stop Meta Computer Use task job ${id}`, undefined); + } + } + + /** + * Start a Meta Computer Use task job and wait for it to complete + * @param params The parameters for the task job + */ + async startAndWait(params: StartMetaComputerUseTaskParams): Promise { + const job = await this.start(params); + const jobId = job.jobId; + if (!jobId) { + throw new HyperbrowserError( + "Failed to start Meta Computer Use task job, could not get job ID" + ); + } + + let failures = 0; + while (true) { + try { + const { status } = await this.getStatus(jobId); + if (status === "completed" || status === "failed" || status === "stopped") { + return await this.get(jobId); + } + failures = 0; + } catch (error) { + failures++; + if (failures >= POLLING_ATTEMPTS) { + throw new HyperbrowserError( + `Failed to poll Meta Computer Use task job ${jobId} after ${POLLING_ATTEMPTS} attempts: ${error}` + ); + } + } + await sleep(2000); + } + } +} diff --git a/src/types/agents/meta-computer-use.ts b/src/types/agents/meta-computer-use.ts new file mode 100644 index 0000000..fbf8cc1 --- /dev/null +++ b/src/types/agents/meta-computer-use.ts @@ -0,0 +1,75 @@ +import { MetaComputerUseLlm, MetaReasoningEffort, MetaComputerUseTaskStatus } from "../constants"; +import { CreateSessionParams } from "../session"; + +export interface MetaComputerUseApiKeys { + meta?: string; +} + +export interface StartMetaComputerUseTaskParams { + task: string; + llm?: MetaComputerUseLlm; + reasoningEffort?: MetaReasoningEffort; + sessionId?: string; + maxFailures?: number; + maxSteps?: number; + keepBrowserOpen?: boolean; + sessionOptions?: CreateSessionParams; + useCustomApiKeys?: boolean; + apiKeys?: MetaComputerUseApiKeys; + useComputerAction?: boolean; +} + +export interface StartMetaComputerUseTaskResponse { + jobId: string; + liveUrl: string | null; +} + +export interface MetaComputerUseTaskStatusResponse { + status: MetaComputerUseTaskStatus; +} + +export interface MetaComputerUseStepError { + code: string; + message: string; +} + +export interface MetaComputerUseStepIncompleteDetails { + reason?: string; +} + +export interface MetaComputerUseStepReasoning { + effort?: string | null; + summary?: string | null; +} + +export interface MetaComputerUseStepResponse { + created_at?: number | null; + completed_at?: number | null; + output_text?: string | null; + error?: MetaComputerUseStepError | null; + incomplete_details?: MetaComputerUseStepIncompleteDetails | null; + model?: string | null; + output?: any[]; + reasoning?: MetaComputerUseStepReasoning | null; + status?: string | null; +} + +export interface MetaComputerUseTaskData { + steps: MetaComputerUseStepResponse[]; + finalResult: string | null; +} + +export interface MetaComputerUseTaskMetadata { + inputTokens?: number | null; + outputTokens?: number | null; + numTaskStepsCompleted?: number | null; +} + +export interface MetaComputerUseTaskResponse { + jobId: string; + status: MetaComputerUseTaskStatus; + metadata?: MetaComputerUseTaskMetadata | null; + data?: MetaComputerUseTaskData | null; + error?: string | null; + liveUrl: string | null; +} diff --git a/src/types/constants.ts b/src/types/constants.ts index 986d02e..2349a89 100644 --- a/src/types/constants.ts +++ b/src/types/constants.ts @@ -23,6 +23,7 @@ export type GeminiComputerUseTaskStatus = | "failed" | "stopped"; export type GrokComputerUseTaskStatus = "pending" | "running" | "completed" | "failed" | "stopped"; +export type MetaComputerUseTaskStatus = "pending" | "running" | "completed" | "failed" | "stopped"; export type ScrapePageStatus = "completed" | "failed" | "pending" | "running"; export type CrawlPageStatus = "completed" | "failed"; export type ScrapeWaitUntil = "load" | "domcontentloaded" | "networkidle"; @@ -110,6 +111,10 @@ export type GrokComputerUseLlm = "grok-4.5"; export type GrokReasoningEffort = "low" | "medium" | "high"; +export type MetaComputerUseLlm = "muse-spark-1.1" | "muse-spark-1.2" | "muse-spark-1.3"; + +export type MetaReasoningEffort = "minimal" | "low" | "medium" | "high" | "xhigh" | "max"; + export type SessionRegion = | "us" | "us-central" diff --git a/src/types/index.ts b/src/types/index.ts index 43db847..5f2502d 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -102,6 +102,19 @@ export { GrokComputerUseApiKeys, GrokComputerUseTaskMetadata, } from "./agents/grok-computer-use"; +export { + StartMetaComputerUseTaskParams, + StartMetaComputerUseTaskResponse, + MetaComputerUseTaskStatusResponse, + MetaComputerUseTaskResponse, + MetaComputerUseTaskData, + MetaComputerUseStepResponse, + MetaComputerUseStepError, + MetaComputerUseStepIncompleteDetails, + MetaComputerUseStepReasoning, + MetaComputerUseApiKeys, + MetaComputerUseTaskMetadata, +} from "./agents/meta-computer-use"; export { BasicResponse, BrowserMemorySize, @@ -244,6 +257,8 @@ export { GeminiComputerUseLlm, GrokComputerUseLlm, GrokReasoningEffort, + MetaComputerUseLlm, + MetaReasoningEffort, ScrapeScreenshotFormat, ScrapeJobStatus, CrawlJobStatus, @@ -264,6 +279,7 @@ export { CuaTaskStatus, GeminiComputerUseTaskStatus, GrokComputerUseTaskStatus, + MetaComputerUseTaskStatus, SessionEventLogType, SessionRegion, BrowserUseVersion, diff --git a/tests/integration/client-http.test.ts b/tests/integration/client-http.test.ts index f62950c..bac03d7 100644 --- a/tests/integration/client-http.test.ts +++ b/tests/integration/client-http.test.ts @@ -60,6 +60,54 @@ const startServer = async (): Promise => { return; } + if (request.method === "POST" && request.url === "/api/task/meta-computer-use") { + sendJson(response, 200, { jobId: "meta_job_123", liveUrl: null }); + return; + } + + if (request.method === "GET" && request.url === "/api/task/meta-computer-use/meta_job_123/status") { + sendJson(response, 200, { status: "completed" }); + return; + } + + if (request.method === "GET" && request.url === "/api/task/meta-computer-use/meta_job_123") { + sendJson(response, 200, { + jobId: "meta_job_123", + status: "completed", + data: { + steps: [ + { + created_at: 1788743504, + completed_at: 1788743511, + output_text: "Second sentence in the Ecology section.", + error: null, + incomplete_details: null, + model: "muse-spark-1.3", + output: [ + { + type: "function_call", + name: "computer.computer", + arguments: '{"actions":[{"action":"type","text":"google.com\\n"}]}', + status: "completed", + }, + ], + reasoning: { effort: "medium", summary: "concise" }, + status: "completed", + }, + ], + finalResult: "done", + }, + error: null, + liveUrl: null, + }); + return; + } + + if (request.method === "PUT" && request.url === "/api/task/meta-computer-use/meta_job_123/stop") { + sendJson(response, 200, { success: true }); + return; + } + if (request.method === "POST" && request.url === "/api/session") { sendJson(response, 200, { id: "52dd29fb-75a2-43f9-9831-8ff377fedb0a", @@ -256,6 +304,77 @@ describe("client HTTP integration", () => { ]); }); + test("Meta Computer Use starts, polls, reads, and stops a task", async () => { + const server = await startServer(); + servers.push(server); + const client = new HyperbrowserClient({ + apiKey: "test-api-key", + baseUrl: server.baseUrl, + }); + + const started = await client.agents.metaComputerUse.start({ + task: "Complete the task", + llm: "muse-spark-1.3", + reasoningEffort: "max", + useCustomApiKeys: true, + apiKeys: { meta: "meta-key" }, + }); + const status = await client.agents.metaComputerUse.getStatus(started.jobId); + const result = await client.agents.metaComputerUse.get(started.jobId); + const stopped = await client.agents.metaComputerUse.stop(started.jobId); + + expect(started).toEqual({ jobId: "meta_job_123", liveUrl: null }); + expect(status).toEqual({ status: "completed" }); + expect(result.data?.finalResult).toBe("done"); + expect(result.data?.steps[0]).toMatchObject({ + created_at: 1788743504, + completed_at: 1788743511, + model: "muse-spark-1.3", + reasoning: { effort: "medium", summary: "concise" }, + }); + expect(result.data?.steps[0].output?.[0]).toMatchObject({ + type: "function_call", + name: "computer.computer", + }); + expect(stopped).toEqual({ success: true }); + expect(server.requests).toEqual([ + { + method: "POST", + url: "/api/task/meta-computer-use", + apiKey: "test-api-key", + contentType: "application/json", + body: { + task: "Complete the task", + llm: "muse-spark-1.3", + reasoningEffort: "max", + useCustomApiKeys: true, + apiKeys: { meta: "meta-key" }, + }, + }, + { + method: "GET", + url: "/api/task/meta-computer-use/meta_job_123/status", + apiKey: "test-api-key", + contentType: "application/json", + body: undefined, + }, + { + method: "GET", + url: "/api/task/meta-computer-use/meta_job_123", + apiKey: "test-api-key", + contentType: "application/json", + body: undefined, + }, + { + method: "PUT", + url: "/api/task/meta-computer-use/meta_job_123/stop", + apiKey: "test-api-key", + contentType: "application/json", + body: undefined, + }, + ]); + }); + test("session create can start from a snapshot", async () => { const server = await startServer(); servers.push(server);