hanzi-browse 2.2.2 → 2.3.0

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>Hanzi Developer Console</title>
7
- <script type="module" crossorigin src="/dashboard/assets/index-C6ONL__u.js"></script>
7
+ <script type="module" crossorigin src="/dashboard/assets/index-dnFOSpJs.js"></script>
8
8
  <link rel="stylesheet" crossorigin href="/dashboard/assets/index-vMQ9aDcG.css">
9
9
  </head>
10
10
  <body>
@@ -50,6 +50,8 @@ export interface LLMResponse {
50
50
  };
51
51
  /** The model that produced this response (for billing attribution) */
52
52
  model?: string;
53
+ /** Raw Gemini response parts — preserves thought signatures for Gemini 3+ */
54
+ _rawGeminiParts?: any[];
53
55
  }
54
56
  export interface CallLLMParams {
55
57
  messages: Message[];
@@ -107,9 +107,15 @@ function convertMessages(messages) {
107
107
  else if (block.type === "tool_use") {
108
108
  const tu = block;
109
109
  toolUseIdToName[tu.id] = tu.name;
110
- parts.push({
111
- functionCall: { name: tu.name, args: tu.input },
112
- });
110
+ // If raw Gemini parts are available (with thought signatures), use them
111
+ if (msg._rawGeminiParts) {
112
+ // Raw parts already added below — skip individual conversion
113
+ }
114
+ else {
115
+ parts.push({
116
+ functionCall: { name: tu.name, args: tu.input },
117
+ });
118
+ }
113
119
  }
114
120
  else if (block.type === "tool_result") {
115
121
  const tr = block;
@@ -141,7 +147,12 @@ function convertMessages(messages) {
141
147
  }
142
148
  }
143
149
  }
144
- if (parts.length > 0) {
150
+ // Gemini 3+: if raw parts with thought signatures are available, use them directly
151
+ // for the model turn (preserves thought_signature fields that Gemini 3 requires)
152
+ if (role === "model" && msg._rawGeminiParts) {
153
+ geminiMessages.push({ role, parts: msg._rawGeminiParts });
154
+ }
155
+ else if (parts.length > 0) {
145
156
  geminiMessages.push({ role, parts });
146
157
  }
147
158
  }
@@ -208,6 +219,7 @@ async function parseGeminiStream(response, onText, signal) {
208
219
  const toolCalls = [];
209
220
  let stopReason = "end_turn";
210
221
  let usage = { input_tokens: 0, output_tokens: 0 };
222
+ let rawModelParts = null; // Gemini 3: preserve thought signatures
211
223
  try {
212
224
  while (true) {
213
225
  if (signal?.aborted) {
@@ -235,6 +247,10 @@ async function parseGeminiStream(response, onText, signal) {
235
247
  if (!candidate)
236
248
  continue;
237
249
  const parts = candidate.content?.parts || [];
250
+ // Capture raw parts for thought signature passthrough (Gemini 3+)
251
+ if (!rawModelParts)
252
+ rawModelParts = [];
253
+ rawModelParts.push(...parts);
238
254
  for (const part of parts) {
239
255
  if (part.text) {
240
256
  currentText += part.text;
@@ -276,7 +292,7 @@ async function parseGeminiStream(response, onText, signal) {
276
292
  if (toolCalls.length > 0) {
277
293
  stopReason = "tool_use";
278
294
  }
279
- return { content, stop_reason: stopReason, usage };
295
+ return { content, stop_reason: stopReason, usage, _rawGeminiParts: rawModelParts || undefined };
280
296
  }
281
297
  // --- Main Call ---
282
298
  /**
@@ -289,7 +305,7 @@ export async function callVertexLLM(params) {
289
305
  if (!vertexConfig) {
290
306
  throw new Error("Vertex AI not initialized. Call initVertex() first.");
291
307
  }
292
- const { messages, system, tools, model = "gemini-2.5-flash", maxTokens = 16384, signal, onText, } = params;
308
+ const { messages, system, tools, model = "gemini-3-flash-preview", maxTokens = 16384, signal, onText, } = params;
293
309
  const { projectId } = vertexConfig;
294
310
  // Use global endpoint — Google routes to whichever region has capacity,
295
311
  // reducing 429s compared to pinning to a single region.
@@ -644,7 +644,23 @@ async function handleCreateTask(body, apiKey, requestId) {
644
644
  executeTool: async (toolName, toolInput) => {
645
645
  const startMs = Date.now();
646
646
  const result = await executeToolViaRelay(toolName, toolInput, browser_session_id);
647
- // Save screenshot from tool result (best-effort)
647
+ const durationMs = Date.now() - startMs;
648
+ // Save tool result content (best-effort) — enables browsing log access via GET /v1/tasks/:id/steps
649
+ const rawOutput = result.output;
650
+ const toolOutput = typeof rawOutput === "string" ? rawOutput
651
+ : rawOutput ? JSON.stringify(rawOutput).slice(0, 50000)
652
+ : "";
653
+ if (toolOutput) {
654
+ S.insertTaskStep({
655
+ taskRunId: taskRun.id,
656
+ step: currentStep,
657
+ status: "tool_output",
658
+ toolName,
659
+ output: toolOutput.slice(0, 50000), // cap at 50KB per step
660
+ durationMs,
661
+ }).catch(() => { });
662
+ }
663
+ // Save screenshot from tool result
648
664
  if (result.screenshot?.data) {
649
665
  S.insertTaskStep({
650
666
  taskRunId: taskRun.id,
@@ -652,7 +668,7 @@ async function handleCreateTask(body, apiKey, requestId) {
652
668
  status: "screenshot",
653
669
  toolName,
654
670
  screenshot: result.screenshot.data,
655
- durationMs: Date.now() - startMs,
671
+ durationMs,
656
672
  }).catch(() => { });
657
673
  }
658
674
  return result;
@@ -737,6 +753,15 @@ async function handleCreateTask(body, apiKey, requestId) {
737
753
  }
738
754
  }
739
755
  if (updated) {
756
+ // Send task_complete to extension so overlay hides
757
+ if (relayConnection) {
758
+ relayConnection.send({
759
+ type: "task_complete",
760
+ targetSessionId: browser_session_id,
761
+ taskId: taskRun.id,
762
+ answer: result.answer,
763
+ });
764
+ }
740
765
  trackManagedEvent("task_completed", apiKey.workspaceId, {
741
766
  steps: result.steps,
742
767
  duration_ms: Date.now() - taskStartedAt,
@@ -969,7 +994,24 @@ async function handleRequest(req, res) {
969
994
  return;
970
995
  }
971
996
  }
972
- // --- Embeddable pairing snippet ---
997
+ // --- Embeddable pairing component (Stripe Elements-style) ---
998
+ if (method === "GET" && url === "/embed.js") {
999
+ const embedPath = join(process.cwd(), "landing/embed.js");
1000
+ if (existsSync(embedPath)) {
1001
+ res.writeHead(200, {
1002
+ "Content-Type": "application/javascript",
1003
+ "Access-Control-Allow-Origin": "*",
1004
+ "Cache-Control": "public, max-age=3600",
1005
+ });
1006
+ res.end(readFileSync(embedPath));
1007
+ }
1008
+ else {
1009
+ res.writeHead(404);
1010
+ res.end("Not found");
1011
+ }
1012
+ return;
1013
+ }
1014
+ // --- Legacy pairing snippet ---
973
1015
  if (method === "GET" && url === "/hanzi-pair.js") {
974
1016
  const snippetPath = join(process.cwd(), "sdk/hanzi-pair.js");
975
1017
  if (existsSync(snippetPath)) {
@@ -1532,7 +1574,16 @@ export async function runInternalTask(params) {
1532
1574
  task,
1533
1575
  url,
1534
1576
  executeTool: async (toolName, toolInput) => {
1535
- return executeToolViaRelay(toolName, toolInput, browserSessionId);
1577
+ const result = await executeToolViaRelay(toolName, toolInput, browserSessionId);
1578
+ // Save tool output for browsing log
1579
+ const rawOutput = result.output;
1580
+ const toolOutput = typeof rawOutput === "string" ? rawOutput
1581
+ : rawOutput ? JSON.stringify(rawOutput).slice(0, 50000)
1582
+ : "";
1583
+ if (toolOutput) {
1584
+ S.insertTaskStep({ taskRunId: taskRun.id, step: currentStep, status: "tool_output", toolName, output: toolOutput.slice(0, 50000) }).catch(() => { });
1585
+ }
1586
+ return result;
1536
1587
  },
1537
1588
  onStep: (step) => {
1538
1589
  currentStep = step.step;
@@ -17,6 +17,10 @@ export function initManagedTelemetry() {
17
17
  dsn: sentryDsn,
18
18
  environment: process.env.NODE_ENV || "development",
19
19
  tracesSampleRate: 0.2,
20
+ beforeSend(event) {
21
+ delete event.server_name;
22
+ return event;
23
+ },
20
24
  });
21
25
  }
22
26
  if (posthogKey) {
package/dist/telemetry.js CHANGED
@@ -17,7 +17,6 @@ const __filename = fileURLToPath(import.meta.url);
17
17
  const __dirname = dirname(__filename);
18
18
  const CONFIG_DIR = join(homedir(), ".hanzi-browse");
19
19
  const CONFIG_PATH = join(CONFIG_DIR, "config.json");
20
- // Placeholder DSNs — will be replaced with real values in Task 8
21
20
  const SENTRY_DSN = "https://2d5504c5db572b0b2709e64f03bdfcc6@o4511120870932480.ingest.us.sentry.io/4511120907698176";
22
21
  const POSTHOG_KEY = "phc_SNXFKD8YOBPvBNWWZnuCe7stDsJJNJ5WS8MujKhajIF";
23
22
  const POSTHOG_HOST = "https://us.i.posthog.com";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hanzi-browse",
3
- "version": "2.2.2",
3
+ "version": "2.3.0",
4
4
  "description": "Give your AI agent a real browser — click, type, fill forms, test workflows, post content, and read authenticated pages",
5
5
  "license": "PolyForm-Noncommercial-1.0.0",
6
6
  "author": "hanzili",