@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,96 @@
1
+ /*************************************************************************************************
2
+
3
+ Welcome to Baml! To use this generated code, please run one of the following:
4
+
5
+ $ npm install @boundaryml/baml
6
+ $ yarn add @boundaryml/baml
7
+ $ pnpm add @boundaryml/baml
8
+
9
+ *************************************************************************************************/
10
+ import { TypeBuilder as _TypeBuilder } from '@boundaryml/baml/type_builder';
11
+ import { DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME } from "./globals";
12
+ export default class TypeBuilder {
13
+ tb;
14
+ AgentContext;
15
+ BugFailureClassification;
16
+ ConnectorInstructions;
17
+ ExtractedData;
18
+ MisalignmentClassification;
19
+ MultiMediaMessage;
20
+ PartialRecipe;
21
+ QueryResponse;
22
+ constructor() {
23
+ this.tb = new _TypeBuilder({
24
+ classes: new Set([
25
+ "AgentContext", "BugFailureClassification", "ConnectorInstructions", "ExtractedData", "MisalignmentClassification", "MultiMediaMessage", "PartialRecipe", "QueryResponse",
26
+ ]),
27
+ enums: new Set([]),
28
+ runtime: DO_NOT_USE_DIRECTLY_UNLESS_YOU_KNOW_WHAT_YOURE_DOING_RUNTIME
29
+ });
30
+ this.AgentContext = this.tb.classViewer("AgentContext", [
31
+ "instructions", "observationContent", "connectorInstructions",
32
+ ]);
33
+ this.BugFailureClassification = this.tb.classViewer("BugFailureClassification", [
34
+ "reasoning", "classification", "title", "expectedResult", "actualResult", "severity",
35
+ ]);
36
+ this.ConnectorInstructions = this.tb.classViewer("ConnectorInstructions", [
37
+ "connectorId", "instructions",
38
+ ]);
39
+ this.ExtractedData = this.tb.classBuilder("ExtractedData", []);
40
+ this.MisalignmentClassification = this.tb.classViewer("MisalignmentClassification", [
41
+ "reasoning", "classification", "fault", "message",
42
+ ]);
43
+ this.MultiMediaMessage = this.tb.classViewer("MultiMediaMessage", [
44
+ "role", "cacheControl", "content",
45
+ ]);
46
+ this.PartialRecipe = this.tb.classBuilder("PartialRecipe", [
47
+ "reasoning",
48
+ ]);
49
+ this.QueryResponse = this.tb.classBuilder("QueryResponse", []);
50
+ }
51
+ __tb() {
52
+ return this.tb._tb();
53
+ }
54
+ string() {
55
+ return this.tb.string();
56
+ }
57
+ literalString(value) {
58
+ return this.tb.literalString(value);
59
+ }
60
+ literalInt(value) {
61
+ return this.tb.literalInt(value);
62
+ }
63
+ literalBool(value) {
64
+ return this.tb.literalBool(value);
65
+ }
66
+ int() {
67
+ return this.tb.int();
68
+ }
69
+ float() {
70
+ return this.tb.float();
71
+ }
72
+ bool() {
73
+ return this.tb.bool();
74
+ }
75
+ list(type) {
76
+ return this.tb.list(type);
77
+ }
78
+ null() {
79
+ return this.tb.null();
80
+ }
81
+ map(key, value) {
82
+ return this.tb.map(key, value);
83
+ }
84
+ union(types) {
85
+ return this.tb.union(types);
86
+ }
87
+ addClass(name) {
88
+ return this.tb.addClass(name);
89
+ }
90
+ addEnum(name) {
91
+ return this.tb.addEnum(name);
92
+ }
93
+ addBaml(baml) {
94
+ this.tb.addBaml(baml);
95
+ }
96
+ }
@@ -0,0 +1,69 @@
1
+ /*************************************************************************************************
2
+
3
+ Welcome to Baml! To use this generated code, please run one of the following:
4
+
5
+ $ npm install @boundaryml/baml
6
+ $ yarn add @boundaryml/baml
7
+ $ pnpm add @boundaryml/baml
8
+
9
+ *************************************************************************************************/
10
+ import type { Image } from "@boundaryml/baml";
11
+ /**
12
+ * Recursively partial type that can be null.
13
+ *
14
+ * @deprecated Use types from the `partial_types` namespace instead, which provides type-safe partial implementations
15
+ * @template T The type to make recursively partial.
16
+ */
17
+ export type RecursivePartialNull<T> = T extends object ? {
18
+ [P in keyof T]?: RecursivePartialNull<T[P]>;
19
+ } : T | null;
20
+ export interface Checked<T, CheckName extends string = string> {
21
+ value: T;
22
+ checks: Record<CheckName, Check>;
23
+ }
24
+ export interface Check {
25
+ name: string;
26
+ expr: string;
27
+ status: "succeeded" | "failed";
28
+ }
29
+ export declare function all_succeeded<CheckName extends string>(checks: Record<CheckName, Check>): boolean;
30
+ export declare function get_checks<CheckName extends string>(checks: Record<CheckName, Check>): Check[];
31
+ export interface AgentContext {
32
+ instructions?: string | null;
33
+ observationContent: MultiMediaMessage[];
34
+ connectorInstructions: ConnectorInstructions[];
35
+ }
36
+ export interface BugFailureClassification {
37
+ reasoning: string;
38
+ classification: "bug";
39
+ title: string;
40
+ expectedResult: string;
41
+ actualResult: string;
42
+ severity: "critical" | "high" | "medium" | "low";
43
+ }
44
+ export interface ConnectorInstructions {
45
+ connectorId: string;
46
+ instructions: string;
47
+ }
48
+ export interface ExtractedData {
49
+ [key: string]: any;
50
+ }
51
+ export interface MisalignmentClassification {
52
+ reasoning: string;
53
+ classification: "misalignment";
54
+ fault: "test" | "agent";
55
+ message: string;
56
+ }
57
+ export interface MultiMediaMessage {
58
+ role: "user" | "assistant";
59
+ cacheControl: boolean;
60
+ content: (Image | string)[];
61
+ }
62
+ export interface PartialRecipe {
63
+ reasoning: string;
64
+ [key: string]: any;
65
+ }
66
+ export interface QueryResponse {
67
+ [key: string]: any;
68
+ }
69
+ export type MultiMediaContentPart = Image | string;
@@ -0,0 +1,15 @@
1
+ /*************************************************************************************************
2
+
3
+ Welcome to Baml! To use this generated code, please run one of the following:
4
+
5
+ $ npm install @boundaryml/baml
6
+ $ yarn add @boundaryml/baml
7
+ $ pnpm add @boundaryml/baml
8
+
9
+ *************************************************************************************************/
10
+ export function all_succeeded(checks) {
11
+ return get_checks(checks).every(check => check.status === "succeeded");
12
+ }
13
+ export function get_checks(checks) {
14
+ return Object.values(checks);
15
+ }
@@ -0,0 +1 @@
1
+ export declare function completeClaudeCodeAuthFlow(): Promise<string>;
@@ -0,0 +1,186 @@
1
+ import { promises as fs } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join, dirname } from 'path';
4
+ import crypto from 'crypto';
5
+ import { bold, cyanBright } from 'ansis';
6
+ import { exec } from 'node:child_process';
7
+ // Simple cross-platform function to open URLs
8
+ function openUrl(url) {
9
+ return new Promise((resolve, reject) => {
10
+ let command;
11
+ switch (process.platform) {
12
+ case 'darwin':
13
+ command = `open "${url}"`;
14
+ break;
15
+ case 'win32':
16
+ command = `start "${url}"`;
17
+ break;
18
+ default:
19
+ command = `xdg-open "${url}"`;
20
+ break;
21
+ }
22
+ exec(command, (error) => {
23
+ if (error) {
24
+ reject(error);
25
+ }
26
+ else {
27
+ resolve();
28
+ }
29
+ });
30
+ });
31
+ }
32
+ // Constants
33
+ const CLIENT_ID = '9d1c250a-e61b-44d9-88ed-5944d1962f5e';
34
+ const CREDS_PATH = join(homedir(), '.magnitude', 'credentials', 'claudeCode.json');
35
+ // 1. Generate PKCE pair
36
+ function generatePKCE() {
37
+ const verifier = crypto.randomBytes(32).toString('base64url');
38
+ const challenge = crypto
39
+ .createHash('sha256')
40
+ .update(verifier)
41
+ .digest('base64url');
42
+ return { verifier, challenge };
43
+ }
44
+ // 2. Get OAuth authorization URL
45
+ function getAuthorizationURL(pkce) {
46
+ const url = new URL('https://claude.ai/oauth/authorize');
47
+ url.searchParams.set('code', 'true');
48
+ url.searchParams.set('client_id', CLIENT_ID);
49
+ url.searchParams.set('response_type', 'code');
50
+ url.searchParams.set('redirect_uri', 'https://console.anthropic.com/oauth/code/callback');
51
+ url.searchParams.set('scope', 'org:create_api_key user:profile user:inference');
52
+ url.searchParams.set('code_challenge', pkce.challenge);
53
+ url.searchParams.set('code_challenge_method', 'S256');
54
+ url.searchParams.set('state', pkce.verifier);
55
+ return url.toString();
56
+ }
57
+ // 3. Exchange authorization code for tokens
58
+ async function exchangeCodeForTokens(code, verifier) {
59
+ const [authCode, state] = code.split('#');
60
+ const response = await fetch('https://console.anthropic.com/v1/oauth/token', {
61
+ method: 'POST',
62
+ headers: { 'Content-Type': 'application/json' },
63
+ body: JSON.stringify({
64
+ code: authCode,
65
+ state: state,
66
+ grant_type: 'authorization_code',
67
+ client_id: CLIENT_ID,
68
+ redirect_uri: 'https://console.anthropic.com/oauth/code/callback',
69
+ code_verifier: verifier,
70
+ }),
71
+ });
72
+ if (!response.ok) {
73
+ throw new Error(`Token exchange failed: ${response.statusText}`);
74
+ }
75
+ const data = await response.json();
76
+ return {
77
+ access_token: data.access_token,
78
+ refresh_token: data.refresh_token,
79
+ expires_at: Date.now() + (data.expires_in * 1000),
80
+ };
81
+ }
82
+ // 4. Refresh access token
83
+ async function refreshAccessToken(refreshToken) {
84
+ const response = await fetch('https://console.anthropic.com/v1/oauth/token', {
85
+ method: 'POST',
86
+ headers: { 'Content-Type': 'application/json' },
87
+ body: JSON.stringify({
88
+ grant_type: 'refresh_token',
89
+ refresh_token: refreshToken,
90
+ client_id: CLIENT_ID,
91
+ }),
92
+ });
93
+ if (!response.ok) {
94
+ throw new Error(`Token refresh failed: ${response.statusText}`);
95
+ }
96
+ const data = await response.json();
97
+ return {
98
+ access_token: data.access_token,
99
+ refresh_token: data.refresh_token,
100
+ expires_at: Date.now() + (data.expires_in * 1000),
101
+ };
102
+ }
103
+ // 5. Save credentials
104
+ async function saveCredentials(creds) {
105
+ await fs.mkdir(dirname(CREDS_PATH), { recursive: true });
106
+ await fs.writeFile(CREDS_PATH, JSON.stringify(creds, null, 2));
107
+ await fs.chmod(CREDS_PATH, 0o600); // Read/write for owner only
108
+ }
109
+ // 6. Load credentials
110
+ async function loadCredentials() {
111
+ try {
112
+ const data = await fs.readFile(CREDS_PATH, 'utf-8');
113
+ return JSON.parse(data);
114
+ }
115
+ catch {
116
+ return null;
117
+ }
118
+ }
119
+ // 7. Get valid access token (refresh if needed)
120
+ async function getValidAccessToken() {
121
+ const creds = await loadCredentials();
122
+ if (!creds)
123
+ return null;
124
+ // If token is still valid, return it
125
+ if (creds.expires_at > Date.now() + 60000) { // 1 minute buffer
126
+ return creds.access_token;
127
+ }
128
+ // Otherwise, refresh it
129
+ try {
130
+ const newCreds = await refreshAccessToken(creds.refresh_token);
131
+ await saveCredentials(newCreds);
132
+ return newCreds.access_token;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ export async function completeClaudeCodeAuthFlow() {
139
+ // Try to get existing valid token
140
+ const existingToken = await getValidAccessToken();
141
+ if (existingToken)
142
+ return existingToken;
143
+ // Otherwise, go through auth flow
144
+ const pkce = generatePKCE();
145
+ const authUrl = getAuthorizationURL(pkce);
146
+ console.log(bold `Claude Code Pro/Max access token missing or expired.`);
147
+ //console.log(cyanBright`Accounts with Max plan can be used for API access.`)
148
+ console.log(cyanBright `Opening browser for authentication...`);
149
+ try {
150
+ await openUrl(authUrl);
151
+ }
152
+ catch (err) {
153
+ console.log('Could not open browser automatically');
154
+ }
155
+ //console.log('Open this URL in your browser:');
156
+ console.log(bold `\nIf browser did not open, visit:`);
157
+ console.log(authUrl);
158
+ console.log(bold `\nPaste the authorization code here:`);
159
+ const code = await new Promise((resolve) => {
160
+ process.stdin.once('data', (data) => {
161
+ resolve(data.toString().trim());
162
+ });
163
+ });
164
+ const creds = await exchangeCodeForTokens(code, pkce.verifier);
165
+ await saveCredentials(creds);
166
+ console.log(bold `\nCredentials saved!`);
167
+ return creds.access_token;
168
+ }
169
+ /*
170
+ // Usage
171
+ const response = await fetch('https://api.anthropic.com/v1/messages', {
172
+ method: 'POST',
173
+ headers: {
174
+ 'Content-Type': 'application/json',
175
+ 'Authorization': `Bearer ${accessToken}`,
176
+ 'anthropic-beta': 'oauth-2025-04-20',
177
+ // make sure to exclude x-api-key
178
+ },
179
+ body: JSON.stringify({
180
+ model: 'claude-3-5-sonnet-20241022',
181
+ messages: [{ role: 'user', content: 'Hello!' }],
182
+ max_tokens: 100,
183
+ }),
184
+ });
185
+
186
+ */
@@ -0,0 +1,39 @@
1
+ import { AgentContext } from "@/ai/baml_client";
2
+ import { Action } from "@/actions/types";
3
+ import { LLMClient, ModelUsage } from "@/ai/types";
4
+ import { ActionDefinition } from "@/actions";
5
+ import { Schema, z } from 'zod';
6
+ import { Image } from '@/memory/image';
7
+ import EventEmitter from "eventemitter3";
8
+ import { MultiMediaContentPart } from "@/memory/rendering";
9
+ interface ModelHarnessOptions {
10
+ llm: LLMClient;
11
+ }
12
+ export interface ModelHarnessEvents {
13
+ 'tokensUsed': (usage: ModelUsage) => {};
14
+ }
15
+ export declare class ModelHarness {
16
+ /**
17
+ * Strong reasoning agent for high level strategy and planning.
18
+ */
19
+ readonly events: EventEmitter<ModelHarnessEvents>;
20
+ private options;
21
+ private collector;
22
+ private cr;
23
+ private baml;
24
+ private logger;
25
+ private prevTotalInputTokens;
26
+ private prevTotalOutputTokens;
27
+ constructor(options: ModelHarnessOptions);
28
+ setup(): Promise<void>;
29
+ describeModel(): string;
30
+ private _reportUsage;
31
+ partialAct<T>(context: AgentContext, // Changed to ModularMemoryContext
32
+ task: string, data: MultiMediaContentPart[], actionVocabulary: ActionDefinition<T>[]): Promise<{
33
+ reasoning: string;
34
+ actions: Action[];
35
+ }>;
36
+ extract<T extends Schema>(instructions: string, schema: T, screenshot: Image, domContent: string): Promise<z.infer<T>>;
37
+ query<T extends Schema>(context: AgentContext, query: string, schema: T): Promise<z.infer<T>>;
38
+ }
39
+ export {};
@@ -0,0 +1,189 @@
1
+ import { convertToBamlClientOptions } from "./util";
2
+ // Import ModularMemoryContext instead of old MemoryContext
3
+ import { b } from "@/ai/baml_client";
4
+ import { Collector, ClientRegistry } from "@boundaryml/baml";
5
+ import logger from "@/logger";
6
+ import TypeBuilder from "./baml_client/type_builder";
7
+ import { z } from 'zod';
8
+ import { convertActionDefinitionsToBaml, convertZodToBaml } from "@/actions/util";
9
+ import EventEmitter from "eventemitter3";
10
+ export class ModelHarness {
11
+ /**
12
+ * Strong reasoning agent for high level strategy and planning.
13
+ */
14
+ events = new EventEmitter();
15
+ options;
16
+ collector;
17
+ cr;
18
+ baml;
19
+ logger;
20
+ prevTotalInputTokens = 0;
21
+ prevTotalOutputTokens = 0;
22
+ constructor(options) {
23
+ this.options = {
24
+ llm: options.llm,
25
+ //promptCaching: options.promptCaching ?? false
26
+ };
27
+ this.logger = logger.child({ name: 'llm' });
28
+ }
29
+ async setup() {
30
+ // Must be called after constructor
31
+ this.collector = new Collector("macro");
32
+ this.cr = new ClientRegistry();
33
+ let bamlClientOptions = await convertToBamlClientOptions(this.options.llm);
34
+ this.cr.addLlmClient('Magnus', this.options.llm.provider === 'claude-code' ? 'anthropic' : this.options.llm.provider, bamlClientOptions, 'DefaultRetryPolicy');
35
+ this.cr.setPrimary('Magnus');
36
+ this.baml = b.withOptions({ collector: this.collector, clientRegistry: this.cr });
37
+ }
38
+ describeModel() {
39
+ return `${this.options.llm.provider}:${'model' in this.options.llm.options ? this.options.llm.options.model : 'unknown'}`;
40
+ }
41
+ _reportUsage() {
42
+ // console.log('this.collector.last', this.collector.last)
43
+ // if (this.collector.last) console.log("calls:", this.collector.last.calls)//console.log("Response: ", this.collector.last.calls[-1].httpResponse);
44
+ //console.log('last call:', this.collector.last?.calls.at(-1)?.httpResponse?.body.json());
45
+ // Get tokens used since last call to reportUsage
46
+ //console.log(this.collector.usage);
47
+ let inputTokens = 0;
48
+ let outputTokens = 0;
49
+ let cacheWriteInputTokens = 0;
50
+ let cacheReadInputTokens = 0;
51
+ if (this.options.llm.provider === 'anthropic' || this.options.llm.provider === 'claude-code') {
52
+ const usage = this.collector.last?.calls.at(-1)?.httpResponse?.body.json().usage;
53
+ //console.log("Usage from Anthropic:", usage);
54
+ if (!usage) {
55
+ // Sometimes apparently this happens? Happened once after extract for example
56
+ logger.warn("No usage returned from Anthropic provider, cached cost may be inaccurate");
57
+ inputTokens = (this.collector.usage.inputTokens ?? 0) - this.prevTotalInputTokens;
58
+ outputTokens = (this.collector.usage.outputTokens ?? 0) - this.prevTotalOutputTokens;
59
+ }
60
+ else {
61
+ inputTokens = usage.input_tokens;
62
+ outputTokens = usage.output_tokens;
63
+ cacheWriteInputTokens = usage.cache_creation_input_tokens;
64
+ cacheReadInputTokens = usage.cache_read_input_tokens;
65
+ }
66
+ }
67
+ else {
68
+ inputTokens = (this.collector.usage.inputTokens ?? 0) - this.prevTotalInputTokens;
69
+ outputTokens = (this.collector.usage.outputTokens ?? 0) - this.prevTotalOutputTokens;
70
+ }
71
+ const model = this.options.llm.options.model ?? 'unknown';
72
+ // Get cost if known
73
+ const knownCostMap = {
74
+ // TODO: track cached savings on Gemini
75
+ 'gemini-2.5-pro': { inputTokens: 1.25, outputTokens: 10.0 },
76
+ 'gemini-2.5-flash': { inputTokens: 0.30, outputTokens: 2.50 },
77
+ 'gemini-2.5-flash-lite': { inputTokens: 0.10, outputTokens: 0.40 },
78
+ 'claude-3.5-sonnet': { inputTokens: 3.00, outputTokens: 15.00, cacheWriteInputTokens: 3.75, cacheReadInputTokens: 0.30 },
79
+ 'claude-3.7-sonnet': { inputTokens: 3.00, outputTokens: 15.00, cacheWriteInputTokens: 3.75, cacheReadInputTokens: 0.30 },
80
+ 'claude-sonnet-4': { inputTokens: 3.00, outputTokens: 15.00, cacheWriteInputTokens: 3.75, cacheReadInputTokens: 0.30 },
81
+ 'claude-haiku-4-5': { inputTokens: 1.00, outputTokens: 5.00, cacheWriteInputTokens: 1.25, cacheReadInputTokens: 0.10 },
82
+ 'claude-opus-4': { inputTokens: 15.00, outputTokens: 75.00, cacheWriteInputTokens: 18.75, cacheReadInputTokens: 1.50 },
83
+ 'gpt-4.1': { inputTokens: 2.00, outputTokens: 8.00 },
84
+ 'gpt-4.1-mini': { inputTokens: 0.40, outputTokens: 1.60 },
85
+ 'gpt-4.1-nano': { inputTokens: 0.10, outputTokens: 0.40 },
86
+ 'gpt-4o': { inputTokens: 3.75, outputTokens: 15.00 },
87
+ // Assuming Nebius prices, may be higher
88
+ 'qwen2.5-vl-72b': { inputTokens: 0.25, outputTokens: 0.75 }
89
+ };
90
+ let inputTokenCost;
91
+ let outputTokenCost;
92
+ let cacheWriteInputTokenCost;
93
+ let cacheReadInputTokenCost;
94
+ for (const [name, costs] of Object.entries(knownCostMap)) {
95
+ if (model.includes(name)) {
96
+ inputTokenCost = costs.inputTokens / 1_000_000;
97
+ outputTokenCost = costs.outputTokens / 1_000_000;
98
+ cacheReadInputTokenCost = costs.cacheReadInputTokens ? costs.cacheReadInputTokens / 1_000_000 : undefined;
99
+ cacheWriteInputTokenCost = costs.cacheWriteInputTokens ? costs.cacheWriteInputTokens / 1_000_000 : undefined;
100
+ }
101
+ }
102
+ // console.log("cacheWriteInputTokenCost:", cacheWriteInputTokenCost);
103
+ // console.log("cacheWriteInputTokens:", cacheWriteInputTokens);
104
+ const usage = {
105
+ llm: {
106
+ provider: this.options.llm.provider,
107
+ model: model
108
+ }, //this.options.llm,
109
+ inputTokens: inputTokens,
110
+ outputTokens: outputTokens,
111
+ ...(cacheWriteInputTokens ? { cacheWriteInputTokens } : {}),
112
+ ...(cacheReadInputTokens ? { cacheReadInputTokens } : {}),
113
+ ...(inputTokenCost ? {
114
+ inputCost: inputTokens * inputTokenCost +
115
+ (cacheWriteInputTokenCost ? cacheWriteInputTokenCost * cacheWriteInputTokens : 0.0) +
116
+ (cacheReadInputTokenCost ? cacheReadInputTokenCost * cacheReadInputTokens : 0.0)
117
+ } : {}),
118
+ ...(outputTokenCost ? { outputCost: outputTokens * outputTokenCost } : {}),
119
+ // ...(cacheWriteInputTokenCost ? { : inputTokens * inputTokenCost } : {}),
120
+ // ...(cacheReadInputTokenCost ? { outputCost: outputTokens * outputTokenCost } : {})
121
+ };
122
+ this.events.emit('tokensUsed', usage);
123
+ //console.log("Usage:", usage);
124
+ this.prevTotalInputTokens += inputTokens;
125
+ this.prevTotalOutputTokens += outputTokens;
126
+ }
127
+ async partialAct(context, // Changed to ModularMemoryContext
128
+ task, data, actionVocabulary) {
129
+ const tb = new TypeBuilder();
130
+ tb.PartialRecipe.addProperty('actions', tb.list(convertActionDefinitionsToBaml(tb, actionVocabulary))).description('Always provide at least one action');
131
+ const start = Date.now();
132
+ // Assuming this.baml.CreatePartialRecipe is now typed to accept ModularMemoryContext
133
+ // after BAML generation picked up changes in planner.baml
134
+ const response = await this.baml.CreatePartialRecipe(context, task, data, this.options.llm.provider === 'claude-code', { tb });
135
+ this.logger.trace(`createPartialRecipe took ${Date.now() - start}ms`);
136
+ // BAML does not carry over action type to @@dynamic of PartialRecipe, so forced cast necssary
137
+ //return response as unknown as { actions: z.infer<ActionDefinition<T>['schema']>[] };//, finished: boolean };
138
+ this._reportUsage();
139
+ return {
140
+ reasoning: response.reasoning, //(response.observations ? response.observations + " " : "") + response.meta_reasoning + " " + response.reasoning,
141
+ actions: response.actions // as z.infer<ActionDefinition<T>['schema']>[]
142
+ };
143
+ }
144
+ async extract(instructions, schema, screenshot, domContent) {
145
+ const tb = new TypeBuilder();
146
+ if (schema instanceof z.ZodObject) {
147
+ // populate ExtractedData with schema KVs instead of wrapping in data key unnecessarily
148
+ for (const [key, fieldSchema] of Object.entries(schema.shape)) {
149
+ tb.ExtractedData.addProperty(key, convertZodToBaml(tb, fieldSchema));
150
+ }
151
+ }
152
+ else {
153
+ // for array or primitive have to wrap data key
154
+ tb.ExtractedData.addProperty('data', convertZodToBaml(tb, schema));
155
+ }
156
+ // } else if (schema instanceof z.ZodArray) {
157
+ // }
158
+ const resp = await this.baml.ExtractData(instructions, await screenshot.toBaml(), domContent, this.options.llm.provider === 'claude-code', { tb });
159
+ this._reportUsage();
160
+ if (schema instanceof z.ZodObject) {
161
+ return resp;
162
+ }
163
+ else {
164
+ return resp.data;
165
+ }
166
+ }
167
+ // ^ extract could prob be a subset of query w trimmed mem
168
+ async query(context, query, schema) {
169
+ const tb = new TypeBuilder();
170
+ if (schema instanceof z.ZodObject) {
171
+ // populate ExtractedData with schema KVs instead of wrapping in data key unnecessarily
172
+ for (const [key, fieldSchema] of Object.entries(schema.shape)) {
173
+ tb.QueryResponse.addProperty(key, convertZodToBaml(tb, fieldSchema));
174
+ }
175
+ }
176
+ else {
177
+ // for array or primitive have to wrap data key
178
+ tb.QueryResponse.addProperty('data', convertZodToBaml(tb, schema));
179
+ }
180
+ const resp = await this.baml.QueryMemory(context, query, this.options.llm.provider === 'claude-code', { tb });
181
+ this._reportUsage();
182
+ if (schema instanceof z.ZodObject) {
183
+ return resp;
184
+ }
185
+ else {
186
+ return resp.data;
187
+ }
188
+ }
189
+ }
@@ -0,0 +1,27 @@
1
+ import { MultiMediaContentPart } from "@/memory/rendering";
2
+ import { ModelHarnessEvents } from "./modelHarness";
3
+ import { LLMClient } from "./types";
4
+ import { ActionDefinition } from "@/actions";
5
+ import { AgentContext } from "./baml_client";
6
+ import { Action } from "@/actions/types";
7
+ import { Image } from '@/memory/image';
8
+ import EventEmitter from "eventemitter3";
9
+ import z from "zod";
10
+ export declare class MultiModelHarness {
11
+ /**
12
+ * Delegates model responsibilites to different LLMs and consolidates their usage
13
+ */
14
+ private roles;
15
+ private uniqueModels;
16
+ readonly events: EventEmitter<ModelHarnessEvents>;
17
+ constructor(clients: LLMClient[]);
18
+ setup(): Promise<void>;
19
+ describe(): string;
20
+ partialAct<T>(context: AgentContext, task: string, data: MultiMediaContentPart[], actionVocabulary: ActionDefinition<T>[]): Promise<{
21
+ reasoning: string;
22
+ actions: Action[];
23
+ }>;
24
+ extract<T extends z.Schema>(instructions: string, schema: T, screenshot: Image, domContent: string): Promise<z.infer<T>>;
25
+ query<T extends z.Schema>(context: AgentContext, query: string, schema: T): Promise<z.infer<T>>;
26
+ get numUniqueModels(): number;
27
+ }
@@ -0,0 +1,52 @@
1
+ import { ModelHarness } from "./modelHarness";
2
+ import { allBrowserAgentRoles } from "./types";
3
+ import EventEmitter from "eventemitter3";
4
+ export class MultiModelHarness {
5
+ /**
6
+ * Delegates model responsibilites to different LLMs and consolidates their usage
7
+ */
8
+ // Roles may reference the same harness
9
+ roles = {};
10
+ uniqueModels = [];
11
+ events = new EventEmitter();
12
+ constructor(clients) {
13
+ // Sort by specificity (from least specific to most specific)
14
+ const sortedClients = clients.toSorted((a, b) => (b.roles ? b.roles.length : 9999) - (a.roles ? a.roles.length : 9999));
15
+ for (const client of sortedClients) {
16
+ const harness = new ModelHarness({ llm: client });
17
+ this.uniqueModels.push(harness);
18
+ if (client.roles) {
19
+ for (const role of client.roles) {
20
+ this.roles[role] = harness;
21
+ }
22
+ }
23
+ else {
24
+ for (const role of allBrowserAgentRoles) {
25
+ this.roles[role] = harness;
26
+ }
27
+ }
28
+ // Forward token usage events upward
29
+ harness.events.on('tokensUsed', (usage) => { this.events.emit('tokensUsed', usage); }, this);
30
+ }
31
+ }
32
+ async setup() {
33
+ await Promise.all(this.uniqueModels.map(model => model.setup()));
34
+ }
35
+ describe() {
36
+ // for now - describe least specific model
37
+ return this.uniqueModels[0].describeModel();
38
+ }
39
+ // TODO: generalize responsibility delegation
40
+ async partialAct(context, task, data, actionVocabulary) {
41
+ return await this.roles['act'].partialAct(context, task, data, actionVocabulary);
42
+ }
43
+ async extract(instructions, schema, screenshot, domContent) {
44
+ return await this.roles['extract'].extract(instructions, schema, screenshot, domContent);
45
+ }
46
+ async query(context, query, schema) {
47
+ return await this.roles['query'].query(context, query, schema);
48
+ }
49
+ get numUniqueModels() {
50
+ return this.uniqueModels.length;
51
+ }
52
+ }