From 1320841b852dd8d30aff8fdb786918846bdda075 Mon Sep 17 00:00:00 2001 From: Matic Zavadlal Date: Fri, 15 Aug 2025 14:58:59 +0100 Subject: [PATCH 1/3] stash --- examples/stream-zod.ts | 90 +++++++++++++++++++++++ examples/stream.ts | 31 +------- examples/{structured-output.ts => zod.ts} | 0 src/resources/tasks.ts | 38 +++++++++- 4 files changed, 125 insertions(+), 34 deletions(-) create mode 100755 examples/stream-zod.ts rename examples/{structured-output.ts => zod.ts} (100%) diff --git a/examples/stream-zod.ts b/examples/stream-zod.ts new file mode 100755 index 0000000..3baea33 --- /dev/null +++ b/examples/stream-zod.ts @@ -0,0 +1,90 @@ +#!/usr/bin/env -S npm run tsn -T + +import { BrowserUse } from 'browser-use-sdk'; +import { TaskViewWithSchema } from 'browser-use-sdk/lib/parse'; +import z from 'zod'; + +const HackerNewsResponse = z.object({ + title: z.string(), + url: z.string(), + score: z.number(), +}); + +const TaskOutput = z.object({ + posts: z.array(HackerNewsResponse), +}); + +async function main() { + // gets API Key from environment variable BROWSER_USE_API_KEY + const browseruse = new BrowserUse(); + + console.log('Creating task and starting stream...\n'); + + // Create a task and get the stream + const stream = browseruse.tasks.stream({ + task: 'Extract top 10 Hacker News posts and return the title, url, and score', + structuredOutputJson: TaskOutput, + }); + + // Get a reader from the stream + const reader = stream.getReader(); + const decoder = new TextDecoder(); + + try { + // Read the stream chunk by chunk + loop: while (true) { + const { done, value } = await reader.read(); + + if (done) { + console.log('\nStream completed'); + break loop; + } + + // Decode the chunk and parse the Server-Sent Events format + const chunk = decoder.decode(value, { stream: true }); + const lines = chunk.split('\n'); + + for (const line of lines) { + if (line.startsWith('event: ')) { + const event = line.slice(7); + process.stdout.write(`\n[${event}] `); + } else if (line.startsWith('data: ')) { + const data = line.slice(6); + if (data.trim() && data !== '{}') { + try { + const parsed = JSON.parse(data) as TaskViewWithSchema; + + process.stdout.write(`${parsed.status}`); + if (parsed.sessionLiveUrl) { + process.stdout.write(` | Live URL: ${parsed.sessionLiveUrl}`); + } + + if (parsed.steps.length > 0) { + const latestStep = parsed.steps[parsed.steps.length - 1]; + process.stdout.write(` | ${latestStep!.nextGoal}`); + } + + if (parsed.status === 'finished') { + process.stdout.write(`\n\nOUTPUT:`); + + for (const post of parsed.doneOutput!.posts) { + process.stdout.write(`\n - ${post.title} (${post.score}) ${post.url}`); + } + + break loop; + } + } catch (e) { + process.stdout.write(`Raw data: ${data}`); + } + } + } + } + } + } catch (error) { + console.error('Error reading stream:', error); + } finally { + reader.releaseLock(); + } +} + +main().catch(console.error); diff --git a/examples/stream.ts b/examples/stream.ts index d04fbfb..947ba4d 100755 --- a/examples/stream.ts +++ b/examples/stream.ts @@ -32,36 +32,7 @@ async function main() { const lines = chunk.split('\n'); for (const line of lines) { - if (line.startsWith('event: ')) { - const event = line.slice(7); - process.stdout.write(`\n[${event}] `); - } else if (line.startsWith('data: ')) { - const data = line.slice(6); - if (data.trim() && data !== '{}') { - try { - const parsed = JSON.parse(data) as BrowserUse.TaskView; - - process.stdout.write(`${parsed.status}`); - if (parsed.sessionLiveUrl) { - process.stdout.write(` | Live URL: ${parsed.sessionLiveUrl}`); - } - - if (parsed.steps.length > 0) { - const latestStep = parsed.steps[parsed.steps.length - 1]; - process.stdout.write(` | ${latestStep!.nextGoal}`); - } - - if (parsed.status === 'finished') { - process.stdout.write(`\n\nOUTPUT: ${parsed.doneOutput}`); - // Close the reader and exit the main loop when task is finished - reader.releaseLock(); - return; - } - } catch (e) { - process.stdout.write(`Raw data: ${data}`); - } - } - } + console.log(line); } } } catch (error) { diff --git a/examples/structured-output.ts b/examples/zod.ts similarity index 100% rename from examples/structured-output.ts rename to examples/zod.ts diff --git a/src/resources/tasks.ts b/src/resources/tasks.ts index 02d7c36..403447d 100644 --- a/src/resources/tasks.ts +++ b/src/resources/tasks.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import type { ZodType } from 'zod'; +import { ZodType } from 'zod'; import { APIPromise } from '../core/api-promise'; import { APIResource } from '../core/resource'; @@ -131,7 +131,15 @@ export class Tasks extends APIResource { } while (true); } - stream(body: TaskCreateParams, options?: RequestOptions) { + stream( + body: TaskCreateParamsWithSchema, + options?: RequestOptions, + ): ReadableStream>; + stream(body: TaskCreateParams, options?: RequestOptions): ReadableStream>; + stream( + body: TaskCreateParams | TaskCreateParamsWithSchema, + options?: RequestOptions, + ): ReadableStream> { const self = this; const enc = new TextEncoder(); @@ -142,12 +150,34 @@ export class Tasks extends APIResource { controller.enqueue(enc.encode(': connected\n\n')); try { - for await (const msg of self.watch(body, { interval: 500 }, options)) { + let req: TaskCreateParams; + + if ( + 'structuredOutputJson' in body && + body.structuredOutputJson != null && + typeof body.structuredOutputJson === 'object' + ) { + req = { + ...body, + structuredOutputJson: stringifyStructuredOutput(body.structuredOutputJson), + }; + } else { + req = body as TaskCreateParams; + } + + for await (const msg of self.watch(req, { interval: 500 }, options)) { if (options?.signal?.aborted) { break; } - const data = JSON.stringify(msg.data); + let data: string; + + if (body.structuredOutputJson != null && typeof body.structuredOutputJson === 'object') { + const parsed = parseStructuredTaskOutput(msg.data, body.structuredOutputJson); + data = JSON.stringify(parsed); + } else { + data = JSON.stringify(msg.data); + } const payload = `event: ${msg.event}\ndata: ${data}\n\n`; controller.enqueue(enc.encode(payload)); From e2efab772466dfd85178438de7f0f39828f70746 Mon Sep 17 00:00:00 2001 From: Matic Zavadlal Date: Fri, 15 Aug 2025 15:21:13 +0100 Subject: [PATCH 2/3] cleanup --- examples/stream-zod.ts | 71 +++++++++------------------------- examples/stream.ts | 34 +++-------------- src/resources/tasks.ts | 87 ++++++++++++++++-------------------------- 3 files changed, 56 insertions(+), 136 deletions(-) diff --git a/examples/stream-zod.ts b/examples/stream-zod.ts index 3baea33..9867523 100755 --- a/examples/stream-zod.ts +++ b/examples/stream-zod.ts @@ -1,7 +1,6 @@ #!/usr/bin/env -S npm run tsn -T import { BrowserUse } from 'browser-use-sdk'; -import { TaskViewWithSchema } from 'browser-use-sdk/lib/parse'; import z from 'zod'; const HackerNewsResponse = z.object({ @@ -26,65 +25,31 @@ async function main() { structuredOutputJson: TaskOutput, }); - // Get a reader from the stream - const reader = stream.getReader(); - const decoder = new TextDecoder(); - - try { - // Read the stream chunk by chunk - loop: while (true) { - const { done, value } = await reader.read(); - - if (done) { - console.log('\nStream completed'); - break loop; - } - - // Decode the chunk and parse the Server-Sent Events format - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split('\n'); - - for (const line of lines) { - if (line.startsWith('event: ')) { - const event = line.slice(7); - process.stdout.write(`\n[${event}] `); - } else if (line.startsWith('data: ')) { - const data = line.slice(6); - if (data.trim() && data !== '{}') { - try { - const parsed = JSON.parse(data) as TaskViewWithSchema; - - process.stdout.write(`${parsed.status}`); - if (parsed.sessionLiveUrl) { - process.stdout.write(` | Live URL: ${parsed.sessionLiveUrl}`); - } + for await (const msg of stream) { + // Regular + process.stdout.write(`${msg.data.status}`); + if (msg.data.sessionLiveUrl) { + process.stdout.write(` | Live URL: ${msg.data.sessionLiveUrl}`); + } - if (parsed.steps.length > 0) { - const latestStep = parsed.steps[parsed.steps.length - 1]; - process.stdout.write(` | ${latestStep!.nextGoal}`); - } + if (msg.data.steps.length > 0) { + const latestStep = msg.data.steps[msg.data.steps.length - 1]; + process.stdout.write(` | ${latestStep!.nextGoal}`); + } - if (parsed.status === 'finished') { - process.stdout.write(`\n\nOUTPUT:`); + process.stdout.write('\n'); - for (const post of parsed.doneOutput!.posts) { - process.stdout.write(`\n - ${post.title} (${post.score}) ${post.url}`); - } + // Output + if (msg.data.status === 'finished') { + process.stdout.write(`\n\nOUTPUT:`); - break loop; - } - } catch (e) { - process.stdout.write(`Raw data: ${data}`); - } - } - } + for (const post of msg.data.doneOutput!.posts) { + process.stdout.write(`\n - ${post.title} (${post.score}) ${post.url}`); } } - } catch (error) { - console.error('Error reading stream:', error); - } finally { - reader.releaseLock(); } + + console.log('\nStream completed'); } main().catch(console.error); diff --git a/examples/stream.ts b/examples/stream.ts index 947ba4d..6c38fad 100755 --- a/examples/stream.ts +++ b/examples/stream.ts @@ -6,40 +6,18 @@ async function main() { // gets API Key from environment variable BROWSER_USE_API_KEY const browseruse = new BrowserUse(); - console.log('Creating task and starting stream...\n'); + console.log('Creating task and starting stream...'); // Create a task and get the stream - const stream = browseruse.tasks.stream({ + const gen = browseruse.tasks.stream({ task: 'What is the weather in San Francisco?', }); - // Get a reader from the stream - const reader = stream.getReader(); - const decoder = new TextDecoder(); - - try { - // Read the stream chunk by chunk - while (true) { - const { done, value } = await reader.read(); - - if (done) { - console.log('\nStream completed'); - break; - } - - // Decode the chunk and parse the Server-Sent Events format - const chunk = decoder.decode(value, { stream: true }); - const lines = chunk.split('\n'); - - for (const line of lines) { - console.log(line); - } - } - } catch (error) { - console.error('Error reading stream:', error); - } finally { - reader.releaseLock(); + for await (const msg of gen) { + console.log(msg); } + + console.log('\nStream completed'); } main().catch(console.error); diff --git a/src/resources/tasks.ts b/src/resources/tasks.ts index 403447d..bc818be 100644 --- a/src/resources/tasks.ts +++ b/src/resources/tasks.ts @@ -134,65 +134,42 @@ export class Tasks extends APIResource { stream( body: TaskCreateParamsWithSchema, options?: RequestOptions, - ): ReadableStream>; - stream(body: TaskCreateParams, options?: RequestOptions): ReadableStream>; + ): AsyncGenerator<{ event: 'status'; data: TaskViewWithSchema }>; stream( + body: TaskCreateParams, + options?: RequestOptions, + ): AsyncGenerator<{ event: 'status'; data: TaskView }>; + async *stream( body: TaskCreateParams | TaskCreateParamsWithSchema, options?: RequestOptions, - ): ReadableStream> { - const self = this; - - const enc = new TextEncoder(); - - const stream = new ReadableStream({ - async start(controller) { - // open the SSE stream quickly - controller.enqueue(enc.encode(': connected\n\n')); - - try { - let req: TaskCreateParams; - - if ( - 'structuredOutputJson' in body && - body.structuredOutputJson != null && - typeof body.structuredOutputJson === 'object' - ) { - req = { - ...body, - structuredOutputJson: stringifyStructuredOutput(body.structuredOutputJson), - }; - } else { - req = body as TaskCreateParams; - } - - for await (const msg of self.watch(req, { interval: 500 }, options)) { - if (options?.signal?.aborted) { - break; - } - - let data: string; - - if (body.structuredOutputJson != null && typeof body.structuredOutputJson === 'object') { - const parsed = parseStructuredTaskOutput(msg.data, body.structuredOutputJson); - data = JSON.stringify(parsed); - } else { - data = JSON.stringify(msg.data); - } - - const payload = `event: ${msg.event}\ndata: ${data}\n\n`; - controller.enqueue(enc.encode(payload)); - } - - controller.enqueue(enc.encode('event: end\ndata: {}\n\n')); - } catch (e) { - controller.enqueue(enc.encode(`event: error\ndata: ${JSON.stringify({ message: String(e) })}\n\n`)); - } finally { - controller.close(); - } - }, - }); + ): AsyncGenerator { + let req: TaskCreateParams; + + if ( + 'structuredOutputJson' in body && + body.structuredOutputJson != null && + typeof body.structuredOutputJson === 'object' + ) { + req = { + ...body, + structuredOutputJson: stringifyStructuredOutput(body.structuredOutputJson), + }; + } else { + req = body as TaskCreateParams; + } - return stream; + for await (const msg of this.watch(req, { interval: 500 }, options)) { + if (options?.signal?.aborted) { + break; + } + + if (body.structuredOutputJson != null && typeof body.structuredOutputJson === 'object') { + const parsed = parseStructuredTaskOutput(msg.data, body.structuredOutputJson); + yield { event: 'status', data: parsed }; + } else { + yield { event: 'status', data: msg.data }; + } + } } /** From 1cd21573ded0d024321ac04b990d156e598674d7 Mon Sep 17 00:00:00 2001 From: Matic Zavadlal Date: Fri, 15 Aug 2025 15:29:37 +0100 Subject: [PATCH 3/3] Update tasks.ts --- src/resources/tasks.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/resources/tasks.ts b/src/resources/tasks.ts index bc818be..9b3e6ea 100644 --- a/src/resources/tasks.ts +++ b/src/resources/tasks.ts @@ -1,6 +1,6 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. -import { ZodType } from 'zod'; +import type { ZodType } from 'zod'; import { APIPromise } from '../core/api-promise'; import { APIResource } from '../core/resource';