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.
- package/dist/agent/loop.js +6 -2
- package/dist/cli/setup.js +10 -1
- package/dist/dashboard/assets/{index-C6ONL__u.js → index-dnFOSpJs.js} +13 -13
- package/dist/dashboard/index.html +1 -1
- package/dist/llm/client.d.ts +2 -0
- package/dist/llm/vertex.js +22 -6
- package/dist/managed/api.js +55 -4
- package/dist/managed/telemetry.js +4 -0
- package/dist/telemetry.js +0 -1
- package/package.json +1 -1
|
@@ -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-
|
|
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>
|
package/dist/llm/client.d.ts
CHANGED
|
@@ -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[];
|
package/dist/llm/vertex.js
CHANGED
|
@@ -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
|
|
111
|
-
|
|
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
|
|
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-
|
|
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.
|
package/dist/managed/api.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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;
|
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.
|
|
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",
|