@ddwang/magnitude-core 0.3.1-ddwang.1

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 (142) hide show
  1. package/dist/actions/desktopActions.d.ts +58 -0
  2. package/dist/actions/desktopActions.js +154 -0
  3. package/dist/actions/index.d.ts +26 -0
  4. package/dist/actions/index.js +11 -0
  5. package/dist/actions/taskActions.d.ts +8 -0
  6. package/dist/actions/taskActions.js +27 -0
  7. package/dist/actions/types.d.ts +31 -0
  8. package/dist/actions/types.js +5 -0
  9. package/dist/actions/util.d.ts +20 -0
  10. package/dist/actions/util.js +297 -0
  11. package/dist/actions/webActions.d.ts +79 -0
  12. package/dist/actions/webActions.js +190 -0
  13. package/dist/agent/browserAgent.d.ts +29 -0
  14. package/dist/agent/browserAgent.js +122 -0
  15. package/dist/agent/errors.d.ts +8 -0
  16. package/dist/agent/errors.js +68 -0
  17. package/dist/agent/index.d.ts +53 -0
  18. package/dist/agent/index.js +377 -0
  19. package/dist/agent/narrator.d.ts +4 -0
  20. package/dist/agent/narrator.js +63 -0
  21. package/dist/agent/pause.test.d.ts +1 -0
  22. package/dist/agent/pause.test.js +194 -0
  23. package/dist/ai/baml_client/async_client.d.ts +73 -0
  24. package/dist/ai/baml_client/async_client.js +152 -0
  25. package/dist/ai/baml_client/async_request.d.ts +35 -0
  26. package/dist/ai/baml_client/async_request.js +98 -0
  27. package/dist/ai/baml_client/config.d.ts +11 -0
  28. package/dist/ai/baml_client/config.js +20 -0
  29. package/dist/ai/baml_client/globals.d.ts +16 -0
  30. package/dist/ai/baml_client/globals.js +37 -0
  31. package/dist/ai/baml_client/index.d.ts +16 -0
  32. package/dist/ai/baml_client/index.js +41 -0
  33. package/dist/ai/baml_client/inlinedbaml.d.ts +19 -0
  34. package/dist/ai/baml_client/inlinedbaml.js +31 -0
  35. package/dist/ai/baml_client/parser.d.ts +53 -0
  36. package/dist/ai/baml_client/parser.js +86 -0
  37. package/dist/ai/baml_client/partial_types.d.ts +61 -0
  38. package/dist/ai/baml_client/partial_types.js +10 -0
  39. package/dist/ai/baml_client/sync_client.d.ts +50 -0
  40. package/dist/ai/baml_client/sync_client.js +100 -0
  41. package/dist/ai/baml_client/sync_request.d.ts +35 -0
  42. package/dist/ai/baml_client/sync_request.js +98 -0
  43. package/dist/ai/baml_client/tracing.d.ts +16 -0
  44. package/dist/ai/baml_client/tracing.js +18 -0
  45. package/dist/ai/baml_client/type_builder.d.ts +38 -0
  46. package/dist/ai/baml_client/type_builder.js +96 -0
  47. package/dist/ai/baml_client/types.d.ts +69 -0
  48. package/dist/ai/baml_client/types.js +15 -0
  49. package/dist/ai/claudeCode.d.ts +1 -0
  50. package/dist/ai/claudeCode.js +186 -0
  51. package/dist/ai/modelHarness.d.ts +39 -0
  52. package/dist/ai/modelHarness.js +189 -0
  53. package/dist/ai/multiModelHarness.d.ts +27 -0
  54. package/dist/ai/multiModelHarness.js +52 -0
  55. package/dist/ai/types.d.ts +90 -0
  56. package/dist/ai/types.js +5 -0
  57. package/dist/ai/util.d.ts +13 -0
  58. package/dist/ai/util.js +165 -0
  59. package/dist/cli.d.ts +0 -0
  60. package/dist/cli.js +3 -0
  61. package/dist/common/actions.d.ts +0 -0
  62. package/dist/common/actions.js +14 -0
  63. package/dist/common/events.d.ts +15 -0
  64. package/dist/common/events.js +3 -0
  65. package/dist/common/failure.d.ts +68 -0
  66. package/dist/common/failure.js +1 -0
  67. package/dist/common/index.d.ts +3 -0
  68. package/dist/common/index.js +3 -0
  69. package/dist/common/retry.d.ts +16 -0
  70. package/dist/common/retry.js +30 -0
  71. package/dist/common/util.d.ts +23 -0
  72. package/dist/common/util.js +132 -0
  73. package/dist/connectors/browserConnector.d.ts +40 -0
  74. package/dist/connectors/browserConnector.js +127 -0
  75. package/dist/connectors/desktopConnector.d.ts +58 -0
  76. package/dist/connectors/desktopConnector.js +68 -0
  77. package/dist/connectors/index.d.ts +10 -0
  78. package/dist/connectors/index.js +2 -0
  79. package/dist/index.cjs +6143 -0
  80. package/dist/index.d.cts +825 -0
  81. package/dist/index.d.ts +20 -0
  82. package/dist/index.js +26 -0
  83. package/dist/index.mjs +6097 -0
  84. package/dist/logger.d.ts +3 -0
  85. package/dist/logger.js +15 -0
  86. package/dist/memory/agentMemory.d.ts +37 -0
  87. package/dist/memory/agentMemory.js +121 -0
  88. package/dist/memory/image.d.ts +26 -0
  89. package/dist/memory/image.js +106 -0
  90. package/dist/memory/index.d.ts +3 -0
  91. package/dist/memory/index.js +3 -0
  92. package/dist/memory/masking.d.ts +7 -0
  93. package/dist/memory/masking.js +141 -0
  94. package/dist/memory/masking.test.d.ts +1 -0
  95. package/dist/memory/masking.test.js +117 -0
  96. package/dist/memory/observation.d.ts +38 -0
  97. package/dist/memory/observation.js +79 -0
  98. package/dist/memory/rendering/index.d.ts +10 -0
  99. package/dist/memory/rendering/index.js +13 -0
  100. package/dist/memory/rendering/renderJsonParts.d.ts +3 -0
  101. package/dist/memory/rendering/renderJsonParts.js +115 -0
  102. package/dist/memory/rendering/renderJsonParts.test.d.ts +1 -0
  103. package/dist/memory/rendering/renderJsonParts.test.js +217 -0
  104. package/dist/memory/rendering/renderXmlParts.d.ts +7 -0
  105. package/dist/memory/rendering/renderXmlParts.js +127 -0
  106. package/dist/memory/serde.d.ts +20 -0
  107. package/dist/memory/serde.js +81 -0
  108. package/dist/memory/util.d.ts +2 -0
  109. package/dist/memory/util.js +17 -0
  110. package/dist/telemetry/events.d.ts +9 -0
  111. package/dist/telemetry/events.js +80 -0
  112. package/dist/telemetry/index.d.ts +6 -0
  113. package/dist/telemetry/index.js +91 -0
  114. package/dist/types.d.ts +30 -0
  115. package/dist/types.js +1 -0
  116. package/dist/util.d.ts +1 -0
  117. package/dist/util.js +12 -0
  118. package/dist/version.d.ts +1 -0
  119. package/dist/version.js +3 -0
  120. package/dist/web/browserProvider.d.ts +23 -0
  121. package/dist/web/browserProvider.js +153 -0
  122. package/dist/web/harness.d.ts +102 -0
  123. package/dist/web/harness.js +340 -0
  124. package/dist/web/stability.d.ts +57 -0
  125. package/dist/web/stability.js +316 -0
  126. package/dist/web/tabs.d.ts +58 -0
  127. package/dist/web/tabs.js +293 -0
  128. package/dist/web/transformer.d.ts +7 -0
  129. package/dist/web/transformer.js +41 -0
  130. package/dist/web/types.d.ts +32 -0
  131. package/dist/web/types.js +2 -0
  132. package/dist/web/util.d.ts +3 -0
  133. package/dist/web/util.js +81 -0
  134. package/dist/web/visualizer/cursor.d.ts +16 -0
  135. package/dist/web/visualizer/cursor.js +152 -0
  136. package/dist/web/visualizer/index.d.ts +22 -0
  137. package/dist/web/visualizer/index.js +47 -0
  138. package/dist/web/visualizer/mouseEffects.d.ts +18 -0
  139. package/dist/web/visualizer/mouseEffects.js +287 -0
  140. package/dist/web/visualizer/typeEffects.d.ts +10 -0
  141. package/dist/web/visualizer/typeEffects.js +190 -0
  142. package/package.json +71 -0
@@ -0,0 +1,81 @@
1
+ import { Image } from './image';
2
+ export async function observableDataToJson(data) {
3
+ if (data instanceof Image) {
4
+ return await data.toJson(); //return imageToJson(data);
5
+ }
6
+ if (typeof data === 'string' || typeof data === 'number' || typeof data === 'boolean') {
7
+ return { type: "primitive", content: data };
8
+ }
9
+ if (data === undefined) {
10
+ return undefined; // JSON.stringify will omit keys with this value or handle standalone undefined
11
+ }
12
+ if (data === null) {
13
+ return null;
14
+ }
15
+ if (Array.isArray(data)) {
16
+ // Filter out undefined items first, then map and process the rest.
17
+ return Promise.all(data
18
+ .filter(item => item !== undefined)
19
+ .map(item => observableDataToJson(item)));
20
+ }
21
+ if (typeof data === 'object') { // Known not to be null or array here
22
+ const processedObject = {};
23
+ for (const key in data) {
24
+ if (Object.prototype.hasOwnProperty.call(data, key)) {
25
+ const value = data[key];
26
+ const processedValue = observableDataToJson(value);
27
+ // Only add the key to the new object if its processed value is not undefined
28
+ if (processedValue !== undefined) {
29
+ processedObject[key] = processedValue;
30
+ }
31
+ }
32
+ }
33
+ return processedObject;
34
+ }
35
+ // Fallback for any unexpected data types not covered by ObservableData.
36
+ // Returning undefined is consistent with how unrepresentable values are handled.
37
+ return undefined;
38
+ }
39
+ // TODO: Implement deserialization, and actually leverage serde for logging stuff
40
+ export async function jsonToObservableData(data) {
41
+ // Handle null and undefined primitives
42
+ if (data === null || data === undefined) {
43
+ return data;
44
+ }
45
+ // Handle arrays by recursively converting each item
46
+ if (Array.isArray(data)) {
47
+ return Promise.all(data.map(item => jsonToObservableData(item)));
48
+ }
49
+ // Handle objects, which can be StoredMedia, StoredPrimitive, or a generic object
50
+ if (typeof data === 'object') {
51
+ // Check for our special typed objects
52
+ if ('type' in data && typeof data.type === 'string') {
53
+ switch (data.type) {
54
+ case 'media':
55
+ // Cast the object to StoredMedia and use the static `fromBase64` method.
56
+ const media = data;
57
+ if (media.storage === 'base64') {
58
+ return Image.fromBase64(media.base64);
59
+ }
60
+ throw new Error(`Unsupported media storage type: ${media.storage}`);
61
+ case 'primitive':
62
+ // Unwrap the primitive value from its container object.
63
+ return data.content;
64
+ }
65
+ }
66
+ // Handle generic observable data object by recursively converting each property's value
67
+ const result = {};
68
+ const keys = Object.keys(data);
69
+ // Process all values concurrently for better performance
70
+ const values = await Promise.all(keys.map(key => jsonToObservableData(data[key])));
71
+ // Reconstruct the object from the processed keys and values
72
+ keys.forEach((key, index) => {
73
+ result[key] = values[index];
74
+ });
75
+ return result;
76
+ }
77
+ // According to the MultiMediaJson type definition, raw primitives (string, number, boolean)
78
+ // are not valid at the top level and should be wrapped in a StoredPrimitive object.
79
+ // Throw an error if we encounter them, as the input is malformed.
80
+ throw new Error(`Invalid MultiMediaJson format: Unexpected primitive value '${data}'.`);
81
+ }
@@ -0,0 +1,2 @@
1
+ import { MultiMediaMessage } from '@/ai/baml_client';
2
+ export declare function mergeMessages(messages: MultiMediaMessage[]): MultiMediaMessage[];
@@ -0,0 +1,17 @@
1
+ export function mergeMessages(messages) {
2
+ if (messages.length === 0)
3
+ return [];
4
+ const merged = [];
5
+ let current = messages[0];
6
+ for (let i = 1; i < messages.length; i++) {
7
+ if (messages[i].role === current.role) {
8
+ current.content.push(...messages[i].content);
9
+ }
10
+ else {
11
+ merged.push(current);
12
+ current = messages[i];
13
+ }
14
+ }
15
+ merged.push(current);
16
+ return merged;
17
+ }
@@ -0,0 +1,9 @@
1
+ import type { Agent } from "@/agent";
2
+ import { LLMClientIdentifier } from "@/ai/types";
3
+ export type UsageReport = {
4
+ llm: LLMClientIdentifier;
5
+ inputTokens: number;
6
+ outputTokens: number;
7
+ numCalls: number;
8
+ }[];
9
+ export declare function telemetrifyAgent(agent: Agent): void;
@@ -0,0 +1,80 @@
1
+ import { createId, sendTelemetry } from "@/telemetry";
2
+ // kinda jank
3
+ function addUsageToReport(report, usage) {
4
+ const modelHash = JSON.stringify(usage.llm);
5
+ let exists = false;
6
+ for (const entry of report) {
7
+ const compare = JSON.stringify(usage.llm);
8
+ if (modelHash === compare) {
9
+ // merge with existing usage
10
+ exists = true;
11
+ entry.inputTokens += usage.inputTokens;
12
+ entry.outputTokens += usage.outputTokens;
13
+ entry.numCalls += 1;
14
+ }
15
+ }
16
+ if (!exists) {
17
+ report.push({ ...usage, numCalls: 1 });
18
+ }
19
+ }
20
+ export function telemetrifyAgent(agent) {
21
+ // Generate ID here that will be used to represent this agent in all telemetry events
22
+ const agentId = createId();
23
+ agent.events.on('start', async () => {
24
+ await sendTelemetry('agent-start', { agentId });
25
+ });
26
+ agent.events.on('stop', async () => {
27
+ await sendTelemetry('agent-stop', { agentId });
28
+ });
29
+ agent.events.on('actStarted', async () => {
30
+ const startedAt = Date.now();
31
+ const report = [];
32
+ let actionCount = 0;
33
+ const tokenListener = (usage) => {
34
+ addUsageToReport(report, usage);
35
+ };
36
+ agent.events.on('tokensUsed', tokenListener);
37
+ const actionListener = () => {
38
+ actionCount++;
39
+ };
40
+ agent.events.on('actionDone', actionListener);
41
+ agent.events.once('actDone', async () => {
42
+ agent.events.removeListener('tokensUsed', tokenListener);
43
+ agent.events.removeListener('actionDone', actionListener);
44
+ const doneAt = Date.now();
45
+ await sendTelemetry('agent-act', {
46
+ agentId,
47
+ startedAt,
48
+ doneAt,
49
+ actionCount,
50
+ modelUsage: report,
51
+ });
52
+ });
53
+ });
54
+ //if (agent instanceof BrowserAgent) {
55
+ if (agent.hasOwnProperty('browserAgentEvents')) {
56
+ // Report nav and extract telemetry if browser agent
57
+ const browserAgent = agent;
58
+ browserAgent.browserAgentEvents.on('nav', async () => {
59
+ await sendTelemetry('agent-nav', { agentId });
60
+ });
61
+ browserAgent.browserAgentEvents.on('extractStarted', async () => {
62
+ const startedAt = Date.now();
63
+ const report = [];
64
+ const tokenListener = (usage) => {
65
+ addUsageToReport(report, usage);
66
+ };
67
+ browserAgent.events.on('tokensUsed', tokenListener);
68
+ browserAgent.browserAgentEvents.once('extractDone', async () => {
69
+ browserAgent.events.removeListener('tokensUsed', tokenListener);
70
+ const doneAt = Date.now();
71
+ await sendTelemetry('agent-extract', {
72
+ agentId,
73
+ startedAt,
74
+ doneAt,
75
+ modelUsage: report,
76
+ });
77
+ });
78
+ });
79
+ }
80
+ }
@@ -0,0 +1,6 @@
1
+ import { PostHog } from 'posthog-node';
2
+ export declare const createId: () => string;
3
+ export declare const posthog: PostHog;
4
+ export declare function getMachineId(): string;
5
+ export declare function getCodebaseId(): string | undefined;
6
+ export declare function sendTelemetry(eventName: string, properties: Record<string, any>): Promise<void>;
@@ -0,0 +1,91 @@
1
+ import logger from '@/logger';
2
+ import * as fs from 'node:fs';
3
+ import * as path from 'node:path';
4
+ import * as os from 'node:os';
5
+ import { execSync } from 'node:child_process';
6
+ import cuid2 from '@paralleldrive/cuid2';
7
+ import { PostHog } from 'posthog-node';
8
+ import { VERSION } from '@/version';
9
+ import { createHash } from 'crypto';
10
+ export const createId = cuid2.init({ length: 12 });
11
+ export const posthog = new PostHog('phc_BTdnTtG68V5QG6sqUNGqGfmjXk8g0ePBRu9FIr9upNu', {
12
+ host: 'https://us.i.posthog.com'
13
+ });
14
+ export function getMachineId() {
15
+ // Define storage location
16
+ const dir = path.join(os.homedir(), '.magnitude');
17
+ const filePath = path.join(dir, 'user.json');
18
+ try {
19
+ // Read existing ID if available
20
+ if (fs.existsSync(filePath)) {
21
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
22
+ if (data.id)
23
+ return data.id;
24
+ }
25
+ // Generate new ID if needed
26
+ //console.log(`generating new ID in ${filePath}`)
27
+ fs.mkdirSync(dir, { recursive: true });
28
+ const id = createId();
29
+ fs.writeFileSync(filePath, JSON.stringify({ id }));
30
+ return id;
31
+ }
32
+ catch {
33
+ // Fallback to temporary ID if storage fails
34
+ return createId();
35
+ }
36
+ }
37
+ export function getCodebaseId() {
38
+ try {
39
+ const command = 'git rev-list --max-parents=0 HEAD';
40
+ const firstCommitHash = execSync(command, {
41
+ encoding: 'utf8',
42
+ stdio: 'pipe',
43
+ }).trim();
44
+ //return firstCommitHash;
45
+ return createHash('sha256').update(firstCommitHash).digest('hex').substring(0, 12);
46
+ }
47
+ catch (error) {
48
+ // ID representing no git repo detected
49
+ return undefined; //'000000000000';
50
+ }
51
+ }
52
+ export async function sendTelemetry(eventName, properties) {
53
+ /**
54
+ * Send telemetry to Posthog with given event name and properites
55
+ * - automatically grouped by codebase derived from SHA256 hash of first git commit if available
56
+ * - telemetryVersion, packageVersion, and codebase all added to properties automatically
57
+ */
58
+ const userId = getMachineId();
59
+ const codebaseId = getCodebaseId();
60
+ if (codebaseId) {
61
+ try {
62
+ posthog.groupIdentify({
63
+ groupType: 'codebase',
64
+ groupKey: codebaseId,
65
+ //properties: {}
66
+ });
67
+ }
68
+ catch (error) {
69
+ logger.warn(`Failed to identify group: ${error.message}`);
70
+ }
71
+ }
72
+ try {
73
+ const props = {
74
+ source: "magnitude-core",
75
+ packageVersion: VERSION,
76
+ //telemetryVersion: "0.1",
77
+ codebase: codebaseId,
78
+ ...properties,
79
+ };
80
+ posthog.capture({
81
+ distinctId: userId,
82
+ event: eventName,
83
+ properties: props,
84
+ ...(codebaseId ? { groups: { codebase: codebaseId } } : {})
85
+ });
86
+ await posthog.shutdown();
87
+ }
88
+ catch (error) {
89
+ logger.warn(`Failed to send telemetry (may have timed out): ${error.message}`);
90
+ }
91
+ }
@@ -0,0 +1,30 @@
1
+ import { FailureDescriptor } from "./common";
2
+ import { Intent } from "./actions/types";
3
+ export interface TestDataEntry {
4
+ key: string;
5
+ value: string;
6
+ sensitive: boolean;
7
+ }
8
+ export interface TestData {
9
+ data?: TestDataEntry[];
10
+ other?: string;
11
+ }
12
+ export interface TestStepDefinition {
13
+ description: string;
14
+ checks: string[];
15
+ testData: TestData;
16
+ }
17
+ export interface TestCaseDefinition {
18
+ url: string;
19
+ steps: TestStepDefinition[];
20
+ recipe?: Intent[];
21
+ }
22
+ export type TestCaseResult = SuccessfulTestCaseResult | FailedTestCaseResult;
23
+ export interface SuccessfulTestCaseResult {
24
+ passed: true;
25
+ recipe: Intent[];
26
+ }
27
+ export interface FailedTestCaseResult {
28
+ passed: false;
29
+ failure: FailureDescriptor;
30
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/dist/util.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function fnv1a32Hex(str: string, seed?: number): string;
package/dist/util.js ADDED
@@ -0,0 +1,12 @@
1
+ export function fnv1a32Hex(str, seed = 0x811c9dc5) {
2
+ const FNV_PRIME_32 = 0x01000193;
3
+ let hash = seed;
4
+ const encoder = new TextEncoder();
5
+ const bytes = encoder.encode(str);
6
+ for (let i = 0; i < bytes.length; i++) {
7
+ hash ^= bytes[i];
8
+ hash = (hash * FNV_PRIME_32) | 0;
9
+ }
10
+ const hexString = (hash >>> 0).toString(16);
11
+ return hexString.padStart(8, '0');
12
+ }
@@ -0,0 +1 @@
1
+ export declare const VERSION: any;
@@ -0,0 +1,3 @@
1
+ import { createRequire } from 'node:module';
2
+ const require = createRequire(import.meta.url);
3
+ export const VERSION = require('../package.json').version;
@@ -0,0 +1,23 @@
1
+ import { Browser, BrowserContext, BrowserContextOptions, LaunchOptions } from "playwright";
2
+ export type BrowserOptions = {
3
+ instance: Browser;
4
+ contextOptions?: BrowserContextOptions;
5
+ } | {
6
+ cdp: string;
7
+ contextOptions?: BrowserContextOptions;
8
+ } | {
9
+ launchOptions?: LaunchOptions;
10
+ contextOptions?: BrowserContextOptions;
11
+ } | {
12
+ context: BrowserContext;
13
+ };
14
+ export declare class BrowserProvider {
15
+ private activeBrowsers;
16
+ private logger;
17
+ private constructor();
18
+ static getInstance(): BrowserProvider;
19
+ private _launchOrReuseBrowser;
20
+ _createAndTrackContext(options: BrowserOptions): Promise<BrowserContext>;
21
+ newContext(options?: BrowserOptions): Promise<BrowserContext>;
22
+ private _applyEmulationSettings;
23
+ }
@@ -0,0 +1,153 @@
1
+ import { chromium } from "playwright";
2
+ import objectHash from 'object-hash';
3
+ import crypto from 'node:crypto';
4
+ import logger from "@/logger";
5
+ const DEFAULT_BROWSER_OPTIONS = {
6
+ headless: false,
7
+ args: ["--disable-gpu", "--disable-blink-features=AutomationControlled"],
8
+ };
9
+ const DEFAULT_BROWSER_CONTEXT_OPTIONS = {
10
+ viewport: { width: 1024, height: 768 },
11
+ };
12
+ export class BrowserProvider {
13
+ activeBrowsers = {};
14
+ logger;
15
+ constructor() {
16
+ this.logger = logger.child({ name: 'browser_provider' });
17
+ }
18
+ static getInstance() {
19
+ if (!globalThis.__magnitude__) {
20
+ globalThis.__magnitude__ = {};
21
+ }
22
+ if (!globalThis.__magnitude__.browserProvider) {
23
+ globalThis.__magnitude__.browserProvider = new BrowserProvider();
24
+ }
25
+ return globalThis.__magnitude__.browserProvider;
26
+ }
27
+ async _launchOrReuseBrowser(options) {
28
+ // hash options
29
+ const hash = objectHash({
30
+ ...options,
31
+ logger: options.logger ? crypto.randomUUID() : '' // replace unserializable logger - use UUID to force re-instance in case different loggers provided
32
+ });
33
+ let activeBrowser;
34
+ if (!(hash in this.activeBrowsers)) {
35
+ this.logger.trace("Launching new browser");
36
+ // Launch new browser, get the PROMISE
37
+ const launchPromise = chromium.launch({ ...DEFAULT_BROWSER_OPTIONS, ...options });
38
+ activeBrowser = {
39
+ browserPromise: launchPromise,
40
+ activeContextsCount: 0
41
+ };
42
+ // add immediately in case others need to await the same one as well
43
+ this.activeBrowsers[hash] = activeBrowser;
44
+ // Wait for browser to fully start
45
+ const browser = await launchPromise;
46
+ browser.on('disconnected', () => {
47
+ delete this.activeBrowsers[hash];
48
+ });
49
+ return activeBrowser;
50
+ }
51
+ else {
52
+ this.logger.trace("Browser with same launch options exists, reusing");
53
+ return this.activeBrowsers[hash];
54
+ }
55
+ }
56
+ async _createAndTrackContext(options) {
57
+ const activeBrowserEntry = await this._launchOrReuseBrowser('launchOptions' in options ? options.launchOptions : {});
58
+ const browser = await activeBrowserEntry.browserPromise;
59
+ const contextOptions = 'contextOptions' in options ? options.contextOptions : undefined;
60
+ const context = await browser.newContext(contextOptions);
61
+ // Get viewport dimensions from context options or use defaults
62
+ const viewport = contextOptions?.viewport || { width: 1024, height: 768 };
63
+ const deviceScaleFactor = contextOptions?.deviceScaleFactor || 1;
64
+ // Apply emulation settings to any new pages created
65
+ context.on('page', async (page) => {
66
+ const cdpSession = await page.context().newCDPSession(page);
67
+ await this._applyEmulationSettings(cdpSession, viewport.width, viewport.height, deviceScaleFactor);
68
+ });
69
+ activeBrowserEntry.activeContextsCount++;
70
+ context.on('close', async () => {
71
+ activeBrowserEntry.activeContextsCount--;
72
+ if (activeBrowserEntry.activeContextsCount <= 0 && browser.isConnected()) {
73
+ await browser.close();
74
+ }
75
+ });
76
+ return context;
77
+ }
78
+ async newContext(options) {
79
+ if (options && 'context' in options) {
80
+ // Context directly provided, we don't need to manage it
81
+ return options.context;
82
+ }
83
+ const dpr = process.env.DEVICE_PIXEL_RATIO ?
84
+ parseInt(process.env.DEVICE_PIXEL_RATIO) :
85
+ process.platform === 'darwin' ? 2 : 1;
86
+ const contextOptions = {
87
+ ...DEFAULT_BROWSER_CONTEXT_OPTIONS,
88
+ deviceScaleFactor: dpr,
89
+ ...(options && 'contextOptions' in options && options.contextOptions ? options.contextOptions : {}) //options.browser?.contextOptions
90
+ };
91
+ options = { ...options, contextOptions };
92
+ if (process.env.MAGNTIUDE_PLAYGROUND) {
93
+ // this.logger.trace("MAGNITUDE_PLAYGROUND environment detected, connecting to browser via CDP");
94
+ // Playground environment - force use CDP on 9222
95
+ //const browser = await chromium.connectOverCDP('http://localhost:9222');
96
+ //return browser.newContext(options?.contextOptions);
97
+ this.logger.trace("MAGNITUDE_PLAYGROUND environment detected, applying playground launch options");
98
+ const playgroundLaunchOptions = {
99
+ args: [
100
+ '--remote-debugging-port=9222',
101
+ '--no-sandbox',
102
+ '--disable-dev-shm-usage',
103
+ '--disable-gpu'
104
+ ]
105
+ };
106
+ // Overwrite any launch options, instance, or cdp configuration with playground launch options
107
+ // Ignore context options (?)
108
+ options = {
109
+ launchOptions: playgroundLaunchOptions
110
+ };
111
+ }
112
+ if ('cdp' in options) {
113
+ const browser = await chromium.connectOverCDP(options.cdp);
114
+ if (browser.contexts().length > 0) {
115
+ return browser.contexts()[0];
116
+ }
117
+ else {
118
+ return browser.newContext(options.contextOptions);
119
+ }
120
+ }
121
+ else if ('instance' in options) {
122
+ const browser = options.instance;
123
+ if (browser.contexts().length > 0) {
124
+ return browser.contexts()[0];
125
+ }
126
+ else {
127
+ return browser.newContext(options.contextOptions);
128
+ }
129
+ }
130
+ else if ('launchOptions' in options) {
131
+ this.logger.trace('Creating context with custom launch options');
132
+ return await this._createAndTrackContext(options);
133
+ }
134
+ else {
135
+ // contextOptions might be passed but no instance | cdp | launchOptions
136
+ this.logger.trace('Creating context for default browser options');
137
+ return await this._createAndTrackContext(options);
138
+ }
139
+ }
140
+ async _applyEmulationSettings(cdpSession, width, height, deviceScaleFactor) {
141
+ await cdpSession.send('Emulation.setDeviceMetricsOverride', {
142
+ width: width,
143
+ height: height,
144
+ deviceScaleFactor: deviceScaleFactor,
145
+ mobile: false,
146
+ screenWidth: width,
147
+ screenHeight: height,
148
+ positionX: 0,
149
+ positionY: 0,
150
+ screenOrientation: { angle: 0, type: 'portraitPrimary' }
151
+ });
152
+ }
153
+ }
@@ -0,0 +1,102 @@
1
+ import { Page, BrowserContext, PageScreenshotOptions } from "playwright";
2
+ import { WebAction } from '@/web/types';
3
+ import { ActionVisualizer, ActionVisualizerOptions } from "./visualizer";
4
+ import { TabState } from "./tabs";
5
+ import { Image } from '@/memory/image';
6
+ import EventEmitter from "eventemitter3";
7
+ export interface WebHarnessOptions {
8
+ virtualScreenDimensions?: {
9
+ width: number;
10
+ height: number;
11
+ };
12
+ visuals?: ActionVisualizerOptions;
13
+ switchTabsOnActivity?: boolean;
14
+ }
15
+ export interface WebHarnessEvents {
16
+ 'activePageChanged': (page: Page) => Promise<void>;
17
+ }
18
+ export declare class WebHarness {
19
+ /**
20
+ * Executes web actions on a page
21
+ * Not responsible for browser lifecycle
22
+ */
23
+ readonly context: BrowserContext;
24
+ private options;
25
+ private stability;
26
+ readonly visualizer: ActionVisualizer;
27
+ private transformer;
28
+ private tabs;
29
+ readonly events: EventEmitter<WebHarnessEvents>;
30
+ constructor(context: BrowserContext, options?: WebHarnessOptions);
31
+ setActivePage(page: Page): Promise<void>;
32
+ retrieveTabState(): Promise<TabState>;
33
+ start(): Promise<void>;
34
+ stop(): Promise<void>;
35
+ get page(): Page;
36
+ screenshot(options?: PageScreenshotOptions): Promise<Image>;
37
+ _type(content: string): Promise<void>;
38
+ transformCoordinates({ x, y }: {
39
+ x: number;
40
+ y: number;
41
+ }): Promise<{
42
+ x: number;
43
+ y: number;
44
+ }>;
45
+ click({ x, y }: {
46
+ x: number;
47
+ y: number;
48
+ }, options?: {
49
+ transform: boolean;
50
+ }): Promise<void>;
51
+ private _click;
52
+ rightClick({ x, y }: {
53
+ x: number;
54
+ y: number;
55
+ }, options?: {
56
+ transform: boolean;
57
+ }): Promise<void>;
58
+ doubleClick({ x, y }: {
59
+ x: number;
60
+ y: number;
61
+ }, options?: {
62
+ transform: boolean;
63
+ }): Promise<void>;
64
+ drag({ x1, y1, x2, y2 }: {
65
+ x1: number;
66
+ y1: number;
67
+ x2: number;
68
+ y2: number;
69
+ }, options?: {
70
+ transform: boolean;
71
+ }): Promise<void>;
72
+ type({ content }: {
73
+ content: string;
74
+ }): Promise<void>;
75
+ clickAndType({ x, y, content }: {
76
+ x: number;
77
+ y: number;
78
+ content: string;
79
+ }, options?: {
80
+ transform: boolean;
81
+ }): Promise<void>;
82
+ scroll({ x, y, deltaX, deltaY }: {
83
+ x: number;
84
+ y: number;
85
+ deltaX: number;
86
+ deltaY: number;
87
+ }, options?: {
88
+ transform: boolean;
89
+ }): Promise<void>;
90
+ switchTab({ index }: {
91
+ index: number;
92
+ }): Promise<void>;
93
+ newTab(): Promise<void>;
94
+ navigate(url: string): Promise<void>;
95
+ selectAll(): Promise<void>;
96
+ enter(): Promise<void>;
97
+ backspace(): Promise<void>;
98
+ tab(): Promise<void>;
99
+ goBack(): Promise<void>;
100
+ executeAction(action: WebAction): Promise<void>;
101
+ waitForStability(timeout?: number): Promise<void>;
102
+ }