@qwen-code/qwen-code 0.18.5-nightly.20260622.6bc3f853e → 0.18.5-preview.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/bundled/qc-helper/SKILL.md +13 -5
- package/bundled/qc-helper/docs/configuration/_meta.ts +1 -0
- package/bundled/qc-helper/docs/configuration/auth.md +68 -48
- package/bundled/qc-helper/docs/configuration/model-providers.md +217 -195
- package/bundled/qc-helper/docs/configuration/qwen-ignore.md +31 -15
- package/bundled/qc-helper/docs/configuration/settings.md +44 -27
- package/bundled/qc-helper/docs/configuration/themes.md +7 -4
- package/bundled/qc-helper/docs/extension/extension-releasing.md +1 -1
- package/bundled/qc-helper/docs/extension/introduction.md +27 -21
- package/bundled/qc-helper/docs/features/arena.md +3 -3
- package/bundled/qc-helper/docs/features/channels/overview.md +1 -1
- package/bundled/qc-helper/docs/features/channels/plugins.md +1 -1
- package/bundled/qc-helper/docs/features/commands.md +6 -1
- package/bundled/qc-helper/docs/features/headless.md +1 -1
- package/bundled/qc-helper/docs/features/mcp.md +33 -14
- package/bundled/qc-helper/docs/features/sandbox.md +2 -2
- package/bundled/qc-helper/docs/features/tool-use-summaries.md +1 -1
- package/bundled/qc-helper/docs/integration-github-action.md +2 -2
- package/bundled/qc-helper/docs/overview.md +1 -1
- package/bundled/qc-helper/docs/quickstart.md +9 -5
- package/bundled/qc-helper/docs/qwen-serve.md +14 -2
- package/bundled/qc-helper/docs/support/tos-privacy.md +1 -1
- package/chunks/{agent-WFLPE5MW.js → agent-P4KSTVXX.js} +24 -22
- package/chunks/{agent-headless-Q27MG73I.js → agent-headless-QUAPWF55.js} +24 -22
- package/chunks/{anthropicContentGenerator-5S4OMMNL.js → anthropicContentGenerator-7352A7I5.js} +12 -11
- package/chunks/artifact-tool-5J4HUZAV.js +675 -0
- package/chunks/{askUserQuestion-WHVYL2S2.js → askUserQuestion-HF7HCWDD.js} +20 -7
- package/chunks/chunk-22IFUCVR.js +291 -0
- package/chunks/{chunk-26ZYKTYZ.js → chunk-2BIF2NVV.js} +2 -2
- package/chunks/{chunk-VKILGMZQ.js → chunk-2QDQTWCI.js} +1 -1
- package/chunks/{chunk-XGU3ETVO.js → chunk-3BR4OIOE.js} +3 -3
- package/chunks/{chunk-LTN3VYMZ.js → chunk-3UALQ56H.js} +1 -1
- package/chunks/{chunk-QBUR6J4B.js → chunk-BC5POO46.js} +8 -7
- package/chunks/{chunk-MSJ2MXJ6.js → chunk-BGUDAULQ.js} +11 -290
- package/chunks/{chunk-FH4P6RQA.js → chunk-BNXANYLH.js} +1 -1
- package/chunks/{chunk-WE6TNQJU.js → chunk-C4K3FEQ2.js} +36 -0
- package/chunks/{chunk-G4BUMVEY.js → chunk-CGEGHF2C.js} +2 -217
- package/chunks/chunk-EEEZIVFJ.js +908 -0
- package/chunks/{chunk-7Z2O7IBO.js → chunk-EIJYMXXX.js} +619 -135
- package/chunks/chunk-EJYJYJZ2.js +3703 -0
- package/chunks/{chunk-ATLUHLZE.js → chunk-EW6C27XX.js} +1 -1
- package/chunks/{chunk-42HGOMED.js → chunk-F6XNTYNE.js} +216 -1
- package/chunks/{chunk-C6MI2ASN.js → chunk-GZCJS5WH.js} +2 -2
- package/chunks/{chunk-KGV3F6UF.js → chunk-HGNSRCRB.js} +1 -1
- package/chunks/{chunk-CX3SGIPK.js → chunk-KBXLIVWQ.js} +2 -2
- package/chunks/{chunk-GOMVJKBL.js → chunk-L7U2RI5O.js} +59 -57
- package/chunks/{chunk-GBN55AWZ.js → chunk-NKJ6LRCR.js} +2 -2
- package/chunks/{chunk-2O3NAOJG.js → chunk-NWLUISRA.js} +1 -1
- package/chunks/{chunk-ID533GA3.js → chunk-OTBK43JR.js} +1 -1
- package/chunks/{chunk-AE6RQU6F.js → chunk-PRYMDRBV.js} +1 -1
- package/chunks/{chunk-KQIKOTQJ.js → chunk-RYLJU2DY.js} +60 -5
- package/chunks/{chunk-CTHZVIMD.js → chunk-T2VDEFUU.js} +2 -2
- package/chunks/{chunk-SWGPABMH.js → chunk-T336V435.js} +80 -83
- package/chunks/{chunk-LF6BKGZT.js → chunk-TXVGVZYR.js} +3 -3
- package/chunks/{chunk-GM3LTU6K.js → chunk-UMRZ7XCR.js} +170 -5015
- package/chunks/{chunk-XU7BKR3B.js → chunk-XU5P23GN.js} +3 -3
- package/chunks/{chunk-EJ6NFQ2L.js → chunk-ZC675NGC.js} +5 -5
- package/chunks/{chunk-CXZV4Q4J.js → chunk-ZXNEOCW2.js} +7118 -5113
- package/chunks/{computer-use-F5O56FVW.js → computer-use-4TLHI3UR.js} +24 -22
- package/chunks/contextCommand-HFEITKSR.js +56 -0
- package/chunks/{cron-create-THNDTYXU.js → cron-create-A5637KCI.js} +12 -9
- package/chunks/{cron-delete-BS5H6TFJ.js → cron-delete-AADOSEOZ.js} +4 -4
- package/chunks/{cron-list-7OBVHT7K.js → cron-list-OFCPTT2Y.js} +4 -4
- package/chunks/{dist-GKRNVFBH.js → dist-ZBX7LPJZ.js} +2 -1
- package/chunks/{edit-UV7MBOJU.js → edit-B7H73V6O.js} +24 -22
- package/chunks/{en-S3OQBISC.js → en-7OGNWWME.js} +48 -0
- package/chunks/{enter-worktree-TSCHZMJ3.js → enter-worktree-GG7ZHYPS.js} +24 -22
- package/chunks/{enterPlanMode-LKS7VIUG.js → enterPlanMode-RHTAKG52.js} +27 -23
- package/chunks/{exit-worktree-Q6EWFLZU.js → exit-worktree-U7VQZRMG.js} +24 -22
- package/chunks/{exitPlanMode-HS6I4D2O.js → exitPlanMode-S35A36II.js} +36 -28
- package/chunks/{geminiContentGenerator-GEZ63VSW.js → geminiContentGenerator-W7KVATBA.js} +4 -4
- package/chunks/{glob-ZN5LKB7E.js → glob-WOFF7ECI.js} +26 -23
- package/chunks/{grep-NIKBBYBU.js → grep-MWNYWYWQ.js} +24 -22
- package/chunks/{keychain-token-storage-YHXIWXIB.js → keychain-token-storage-CSYMNRPP.js} +2 -2
- package/chunks/{loop-wakeup-SSBWZ64K.js → loop-wakeup-DGSMT4N6.js} +5 -5
- package/chunks/{ls-YPFUHZRQ.js → ls-UGRZ4ZOM.js} +6 -6
- package/chunks/{lsp-FD6DNVAF.js → lsp-72IJURW3.js} +2 -2
- package/chunks/{monitor-A25XKPU5.js → monitor-AYUINLYN.js} +24 -22
- package/chunks/{notebook-edit-E72AIOUS.js → notebook-edit-EP4D4WFD.js} +25 -23
- package/chunks/{openaiContentGenerator-ZDLZDXLU.js → openaiContentGenerator-SLYYF7AM.js} +12 -15
- package/chunks/{qwenContentGenerator-HVJPPMTT.js → qwenContentGenerator-VZ7JYU4A.js} +26 -24
- package/chunks/{qwenOAuth2-WZ54YZ4J.js → qwenOAuth2-K4AASPCV.js} +5 -4
- package/chunks/{read-file-L6FM3KUX.js → read-file-65EQ53KS.js} +9 -8
- package/chunks/{ripGrep-YRJCUISE.js → ripGrep-5XLHJDDM.js} +24 -22
- package/chunks/{scheduler-M3742P2I.js → scheduler-5GLJSA2H.js} +73 -27
- package/chunks/{send-message-FWFTX62Q.js → send-message-URUZ7PLA.js} +24 -4
- package/chunks/{serve-ZIP2GDFY.js → serve-MUROZGCG.js} +618 -92
- package/chunks/{shell-T5VSPJLU.js → shell-ET3BQWKB.js} +24 -22
- package/chunks/{skill-ETTFYDOV.js → skill-YEYABCAF.js} +11 -10
- package/chunks/{src-UKOPSYDK.js → src-POVOI65Y.js} +140 -67
- package/chunks/{syntheticOutput-JY5NEDTV.js → syntheticOutput-65FRJGWM.js} +3 -3
- package/chunks/{task-create-IGILDV25.js → task-create-IH45HOFT.js} +6 -6
- package/chunks/{task-list-KBEJVAAG.js → task-list-FUB6JWSX.js} +5 -5
- package/chunks/{task-stop-WFTMQ6AC.js → task-stop-P6IOHQM3.js} +2 -2
- package/chunks/{task-update-UIVX7ZEV.js → task-update-RTUT7LJV.js} +6 -6
- package/chunks/{team-create-DRKUVGBN.js → team-create-EFZFKQFP.js} +28 -26
- package/chunks/{team-delete-LPOXDPVB.js → team-delete-RXGYRMHC.js} +5 -5
- package/chunks/{todoWrite-R4RIJKOD.js → todoWrite-O6JK24G6.js} +4 -4
- package/chunks/{tool-search-EIIK27ON.js → tool-search-BRJC42TE.js} +9 -8
- package/chunks/{web-fetch-JU7SS6KE.js → web-fetch-KUMBROUI.js} +4 -4
- package/chunks/{workflow-4RGAYFSJ.js → workflow-UDRBNFTT.js} +557 -97
- package/chunks/{write-file-GLQYJFEZ.js → write-file-F7TAHAHF.js} +24 -22
- package/chunks/{zh-QXLZV2PX.js → zh-3RMSEO5M.js} +48 -0
- package/chunks/{zh-TW-3OKU7LDA.js → zh-TW-R7WRLTJS.js} +48 -0
- package/cli.js +8473 -5091
- package/locales/en.js +69 -0
- package/locales/zh-TW.js +66 -0
- package/locales/zh.js +66 -0
- package/package.json +3 -2
- package/web-shell/assets/{arc-CRFN10gD.js → arc-DHUeMnnj.js} +1 -1
- package/web-shell/assets/{architectureDiagram-3BPJPVTR-IRlu1CpH.js → architectureDiagram-3BPJPVTR-DlX12yyj.js} +1 -1
- package/web-shell/assets/{blockDiagram-GPEHLZMM-htIZ6iw8.js → blockDiagram-GPEHLZMM-DTFUXMUT.js} +1 -1
- package/web-shell/assets/{c4Diagram-AAUBKEIU-A2GoRq3O.js → c4Diagram-AAUBKEIU-CWdB54fk.js} +1 -1
- package/web-shell/assets/channel-C3WhMjjP.js +1 -0
- package/web-shell/assets/{chunk-2J33WTMH-CdP2AC6u.js → chunk-2J33WTMH-CF5C5jNR.js} +1 -1
- package/web-shell/assets/{chunk-4BX2VUAB-DrA07nW2.js → chunk-4BX2VUAB-Cw7Ydfji.js} +1 -1
- package/web-shell/assets/{chunk-55IACEB6-CFGnVU2f.js → chunk-55IACEB6-CnbI1ZCw.js} +1 -1
- package/web-shell/assets/{chunk-727SXJPM-BTMkq3Tk.js → chunk-727SXJPM-DmSy00Pe.js} +1 -1
- package/web-shell/assets/{chunk-AQP2D5EJ-C9sYg04Z.js → chunk-AQP2D5EJ-DgNgXqBu.js} +1 -1
- package/web-shell/assets/{chunk-FMBD7UC4-Cag-cj6I.js → chunk-FMBD7UC4-tz2IvOXv.js} +1 -1
- package/web-shell/assets/{chunk-ND2GUHAM-CDSTFoR7.js → chunk-ND2GUHAM-DaBLEBPa.js} +1 -1
- package/web-shell/assets/{chunk-QZHKN3VN-CN9Z6oOs.js → chunk-QZHKN3VN-DVCQST3O.js} +1 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-CQ6TXPif.js +1 -0
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CQ6TXPif.js +1 -0
- package/web-shell/assets/{cose-bilkent-S5V4N54A-DuN9j8Du.js → cose-bilkent-S5V4N54A-CAKTcdCQ.js} +1 -1
- package/web-shell/assets/{dagre-BM42HDAG-ByCWe45f.js → dagre-BM42HDAG-BdkBwlKg.js} +1 -1
- package/web-shell/assets/{diagram-2AECGRRQ-eWyU_pKZ.js → diagram-2AECGRRQ-DYt3G65F.js} +1 -1
- package/web-shell/assets/{diagram-5GNKFQAL-0Bfu3Fq5.js → diagram-5GNKFQAL-BWvROWjY.js} +1 -1
- package/web-shell/assets/{diagram-KO2AKTUF-B6KBKkQP.js → diagram-KO2AKTUF-BvvgwIaQ.js} +1 -1
- package/web-shell/assets/{diagram-LMA3HP47-TToDmeA_.js → diagram-LMA3HP47-E7NLSNKg.js} +1 -1
- package/web-shell/assets/{diagram-OG6HWLK6-CQCkC2Yx.js → diagram-OG6HWLK6-CYmUBgpE.js} +1 -1
- package/web-shell/assets/{erDiagram-TEJ5UH35-Cm9ls1iB.js → erDiagram-TEJ5UH35-BU5gY30h.js} +1 -1
- package/web-shell/assets/{flowDiagram-I6XJVG4X-Dyoz80Pv.js → flowDiagram-I6XJVG4X-V_ZssSZ9.js} +1 -1
- package/web-shell/assets/{ganttDiagram-6RSMTGT7-Cfut9yaU.js → ganttDiagram-6RSMTGT7-QIShlsdI.js} +1 -1
- package/web-shell/assets/{gitGraphDiagram-PVQCEYII-BCuJdlfD.js → gitGraphDiagram-PVQCEYII-DJxdu2YS.js} +1 -1
- package/web-shell/assets/index-BPnhI7gl.css +1 -0
- package/web-shell/assets/index-De4rqTsW.js +706 -0
- package/web-shell/assets/{infoDiagram-5YYISTIA-CXKxF2VL.js → infoDiagram-5YYISTIA-BqjdrFeO.js} +1 -1
- package/web-shell/assets/{ishikawaDiagram-YF4QCWOH-DE7HWSU8.js → ishikawaDiagram-YF4QCWOH-BP_a2YZ2.js} +1 -1
- package/web-shell/assets/{journeyDiagram-JHISSGLW-D__YUfds.js → journeyDiagram-JHISSGLW-BXg-S3Lk.js} +1 -1
- package/web-shell/assets/{kanban-definition-UN3LZRKU-D82R169M.js → kanban-definition-UN3LZRKU-BksAkdEJ.js} +1 -1
- package/web-shell/assets/{linear-DpyMbKqT.js → linear-D2m9-ODE.js} +1 -1
- package/web-shell/assets/{mermaid.core-D1KEFDhn.js → mermaid.core-dTtkZp2F.js} +5 -5
- package/web-shell/assets/{mindmap-definition-RKZ34NQL-BwVUFGFE.js → mindmap-definition-RKZ34NQL-Bft9my8O.js} +1 -1
- package/web-shell/assets/{pieDiagram-4H26LBE5-BO_qoEGx.js → pieDiagram-4H26LBE5-Cw8S-alZ.js} +1 -1
- package/web-shell/assets/{quadrantDiagram-W4KKPZXB-Cx0ZaWPo.js → quadrantDiagram-W4KKPZXB-DaDoTMts.js} +1 -1
- package/web-shell/assets/{requirementDiagram-4Y6WPE33-BJPXejOA.js → requirementDiagram-4Y6WPE33-Cp5kSpwj.js} +1 -1
- package/web-shell/assets/{sankeyDiagram-5OEKKPKP-C1xeIL9i.js → sankeyDiagram-5OEKKPKP-CW8RsJJZ.js} +1 -1
- package/web-shell/assets/{sequenceDiagram-3UESZ5HK-CSYKQUjf.js → sequenceDiagram-3UESZ5HK-DTfpIiJK.js} +1 -1
- package/web-shell/assets/{stateDiagram-AJRCARHV-d9hiHlAW.js → stateDiagram-AJRCARHV-DixFVzEs.js} +1 -1
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DZgE04s3.js +1 -0
- package/web-shell/assets/{timeline-definition-PNZ67QCA-BhCBkq8k.js → timeline-definition-PNZ67QCA-DYsbXn5l.js} +1 -1
- package/web-shell/assets/{vennDiagram-CIIHVFJN-DPPFWp_y.js → vennDiagram-CIIHVFJN-HUpRpuUp.js} +1 -1
- package/web-shell/assets/{wardley-L42UT6IY-CcOUIgXY.js → wardley-L42UT6IY-cT3nQH_T.js} +1 -1
- package/web-shell/assets/{wardleyDiagram-YWT4CUSO-p7oR5X7r.js → wardleyDiagram-YWT4CUSO-Dmm7J6Qc.js} +1 -1
- package/web-shell/assets/{xychartDiagram-2RQKCTM6-Dfr7-SEy.js → xychartDiagram-2RQKCTM6-CX0L5p9C.js} +1 -1
- package/web-shell/index.html +3 -3
- package/chunks/chunk-77WXWU44.js +0 -26
- package/chunks/contextCommand-WBVNKL2C.js +0 -54
- package/web-shell/assets/channel-WwImfsc7.js +0 -1
- package/web-shell/assets/classDiagram-4FO5ZUOK-CJKQYFeJ.js +0 -1
- package/web-shell/assets/classDiagram-v2-Q7XG4LA2-CJKQYFeJ.js +0 -1
- package/web-shell/assets/index-BDZiTAkl.css +0 -1
- package/web-shell/assets/index-DaebdpHq.js +0 -516
- package/web-shell/assets/stateDiagram-v2-BHNVJYJU-DgReoybQ.js +0 -1
|
@@ -0,0 +1,675 @@
|
|
|
1
|
+
// Force strict mode and setup for ESM
|
|
2
|
+
"use strict";
|
|
3
|
+
import {
|
|
4
|
+
BaseDeclarativeTool,
|
|
5
|
+
BaseToolInvocation,
|
|
6
|
+
ToolDisplayNames,
|
|
7
|
+
ToolNames
|
|
8
|
+
} from "./chunk-L7U2RI5O.js";
|
|
9
|
+
import {
|
|
10
|
+
openBrowserSecurely
|
|
11
|
+
} from "./chunk-22IFUCVR.js";
|
|
12
|
+
import {
|
|
13
|
+
Storage,
|
|
14
|
+
createDebugLogger,
|
|
15
|
+
getErrorMessage,
|
|
16
|
+
isAbortError,
|
|
17
|
+
isNodeError,
|
|
18
|
+
makeRelative,
|
|
19
|
+
shortenPath,
|
|
20
|
+
unescapePath
|
|
21
|
+
} from "./chunk-C4K3FEQ2.js";
|
|
22
|
+
import "./chunk-Z2Z3GUXZ.js";
|
|
23
|
+
import {
|
|
24
|
+
init_esbuild_shims
|
|
25
|
+
} from "./chunk-A4BMJM77.js";
|
|
26
|
+
import {
|
|
27
|
+
__name
|
|
28
|
+
} from "./chunk-J2S4EL5Y.js";
|
|
29
|
+
|
|
30
|
+
// packages/core/src/tools/artifact/artifact-tool.ts
|
|
31
|
+
init_esbuild_shims();
|
|
32
|
+
import path4 from "node:path";
|
|
33
|
+
|
|
34
|
+
// packages/core/src/tools/artifact/html.ts
|
|
35
|
+
init_esbuild_shims();
|
|
36
|
+
var MAX_ARTIFACT_BYTES = 16 * 1024 * 1024;
|
|
37
|
+
var CSS_RESET = `*,*::before,*::after{box-sizing:border-box}
|
|
38
|
+
html{-webkit-text-size-adjust:100%}
|
|
39
|
+
body{margin:0;padding:1.5rem;font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5;color:#1a1a1a;background:#fff}
|
|
40
|
+
img,svg,video,canvas{max-width:100%;height:auto}
|
|
41
|
+
pre,table{max-width:100%;overflow-x:auto}
|
|
42
|
+
:where(a){color:#0969da}`;
|
|
43
|
+
var DEFAULT_TITLE = "Artifact";
|
|
44
|
+
function sanitizeArtifactTitle(raw) {
|
|
45
|
+
const cleaned = (raw ?? "").replace(/\s+/g, " ").trim().slice(0, 120);
|
|
46
|
+
return cleaned || DEFAULT_TITLE;
|
|
47
|
+
}
|
|
48
|
+
__name(sanitizeArtifactTitle, "sanitizeArtifactTitle");
|
|
49
|
+
function escapeForTitle(s) {
|
|
50
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
51
|
+
}
|
|
52
|
+
__name(escapeForTitle, "escapeForTitle");
|
|
53
|
+
function validateSelfContained(fragment) {
|
|
54
|
+
if (!fragment.trim()) {
|
|
55
|
+
return "Artifact file is empty \u2014 write the page content (a body-only HTML fragment) first.";
|
|
56
|
+
}
|
|
57
|
+
const scan = normalizeAttributeQuotes(fragment);
|
|
58
|
+
const head = fragment.replace(/^\s+/, "").replace(/^(?:<!--[\s\S]*?-->\s*)+/, "");
|
|
59
|
+
const wrapperTag = /^(?:<!doctype\b|<html[\s>]|<head[\s>]|<body[\s>])/i.exec(
|
|
60
|
+
head
|
|
61
|
+
);
|
|
62
|
+
if (wrapperTag) {
|
|
63
|
+
return `Write a body-only fragment \u2014 it starts with a full-document tag (${wrapperTag[0].trim()}). Omit <!doctype>, <html>, <head>, and <body>; they are added at publish time.`;
|
|
64
|
+
}
|
|
65
|
+
const extResource = /\b(?:src|srcset|poster)\s*=\s*["']?\s*(?:https?:)?\/\//i.exec(scan) ?? /<link\b[^>]*\bhref\s*=\s*["']?\s*(?:https?:)?\/\//i.exec(scan);
|
|
66
|
+
if (extResource) {
|
|
67
|
+
return `Artifact must be self-contained \u2014 found an external reference (${truncate(extResource[0])}). Inline scripts/styles and embed assets as data: URIs.`;
|
|
68
|
+
}
|
|
69
|
+
const jsUri = /\b(?:href|src)\s*=\s*["']?\s*javascript\s*:/i.exec(scan);
|
|
70
|
+
if (jsUri) {
|
|
71
|
+
return `Artifact must be self-contained \u2014 found a javascript: URI (${truncate(jsUri[0])}). Use inline <script> blocks instead.`;
|
|
72
|
+
}
|
|
73
|
+
const extScript = /\b(?:fetch|WebSocket|XMLHttpRequest)\s*\(\s*["']\s*(?:https?|wss?):\/\//i.exec(
|
|
74
|
+
scan
|
|
75
|
+
) ?? /\bimport\s*\(\s*["'](?:https?:)?\/\//i.exec(scan) ?? /\bwindow\.open\s*\(/i.exec(scan) ?? /\blocation\.\w+\s*[=(]/i.exec(scan) ?? /\bnavigator\.sendBeacon\s*\(\s*["']\s*(?:https?:)?\/\//i.exec(scan);
|
|
76
|
+
if (extScript) {
|
|
77
|
+
return `Artifact must be self-contained \u2014 found browser network egress (${truncate(extScript[0])}). Embed data in the artifact instead of fetching it at runtime.`;
|
|
78
|
+
}
|
|
79
|
+
const metaRefresh = /<meta\b[^>]*http-equiv\s*=\s*["']?refresh["']?[^>]*\burl\s*=\s*(?:https?:)?\/\//i.exec(
|
|
80
|
+
scan
|
|
81
|
+
);
|
|
82
|
+
if (metaRefresh) {
|
|
83
|
+
return `Artifact must be self-contained \u2014 found a meta refresh redirect (${truncate(metaRefresh[0])}).`;
|
|
84
|
+
}
|
|
85
|
+
const extCss = /(?:@import\s+(?:url\()?|url\()\s*["']?\s*(?:https?:)?\/\//i.exec(scan);
|
|
86
|
+
if (extCss) {
|
|
87
|
+
return `Artifact must be self-contained \u2014 found an external CSS reference (${truncate(extCss[0])}). Inline CSS and embed fonts/images as data: URIs.`;
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
__name(validateSelfContained, "validateSelfContained");
|
|
92
|
+
function normalizeAttributeQuotes(s) {
|
|
93
|
+
return s.replace(/"|"|"/gi, '"').replace(/'|'|'/gi, "'");
|
|
94
|
+
}
|
|
95
|
+
__name(normalizeAttributeQuotes, "normalizeAttributeQuotes");
|
|
96
|
+
function truncate(s, max = 60) {
|
|
97
|
+
const t = s.replace(/\s+/g, " ").trim();
|
|
98
|
+
return t.length > max ? `${t.slice(0, max)}\u2026` : t;
|
|
99
|
+
}
|
|
100
|
+
__name(truncate, "truncate");
|
|
101
|
+
function wrapArtifactHtml(bodyFragment, title) {
|
|
102
|
+
const safeTitle = escapeForTitle(sanitizeArtifactTitle(title));
|
|
103
|
+
return `<!doctype html>
|
|
104
|
+
<html lang="en">
|
|
105
|
+
<head>
|
|
106
|
+
<meta charset="utf-8">
|
|
107
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
108
|
+
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; img-src data:; font-src data:; media-src data:; connect-src 'none'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'; sandbox allow-scripts;">
|
|
109
|
+
<title>${safeTitle}</title>
|
|
110
|
+
<style>${CSS_RESET}</style>
|
|
111
|
+
</head>
|
|
112
|
+
<body>
|
|
113
|
+
${bodyFragment}
|
|
114
|
+
</body>
|
|
115
|
+
</html>
|
|
116
|
+
`;
|
|
117
|
+
}
|
|
118
|
+
__name(wrapArtifactHtml, "wrapArtifactHtml");
|
|
119
|
+
function byteLength(s) {
|
|
120
|
+
return Buffer.byteLength(s, "utf8");
|
|
121
|
+
}
|
|
122
|
+
__name(byteLength, "byteLength");
|
|
123
|
+
|
|
124
|
+
// packages/core/src/tools/artifact/publisher.ts
|
|
125
|
+
init_esbuild_shims();
|
|
126
|
+
import { createHash } from "node:crypto";
|
|
127
|
+
import path from "node:path";
|
|
128
|
+
function artifactIdFromPath(filePath) {
|
|
129
|
+
const normalized = path.resolve(filePath);
|
|
130
|
+
return createHash("sha1").update(normalized).digest("hex").slice(0, 16);
|
|
131
|
+
}
|
|
132
|
+
__name(artifactIdFromPath, "artifactIdFromPath");
|
|
133
|
+
|
|
134
|
+
// packages/core/src/tools/artifact/create-publisher.ts
|
|
135
|
+
init_esbuild_shims();
|
|
136
|
+
|
|
137
|
+
// packages/core/src/tools/artifact/local-publisher.ts
|
|
138
|
+
init_esbuild_shims();
|
|
139
|
+
import { promises as fs } from "node:fs";
|
|
140
|
+
import path2 from "node:path";
|
|
141
|
+
import { pathToFileURL } from "node:url";
|
|
142
|
+
var LocalPublisher = class {
|
|
143
|
+
/** @param baseDir Override the output root (defaults to ~/.qwen/artifacts). */
|
|
144
|
+
constructor(baseDir) {
|
|
145
|
+
this.baseDir = baseDir;
|
|
146
|
+
}
|
|
147
|
+
static {
|
|
148
|
+
__name(this, "LocalPublisher");
|
|
149
|
+
}
|
|
150
|
+
kind = "local";
|
|
151
|
+
getBaseDir() {
|
|
152
|
+
return this.baseDir ?? path2.join(Storage.getGlobalQwenDir(), "artifacts");
|
|
153
|
+
}
|
|
154
|
+
async publish(input) {
|
|
155
|
+
const dir = path2.join(this.getBaseDir(), input.id);
|
|
156
|
+
await fs.mkdir(dir, { recursive: true });
|
|
157
|
+
const filePath = path2.join(dir, "index.html");
|
|
158
|
+
await fs.writeFile(filePath, input.html, "utf8");
|
|
159
|
+
return {
|
|
160
|
+
id: input.id,
|
|
161
|
+
url: pathToFileURL(filePath).href,
|
|
162
|
+
filePath
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
// packages/core/src/tools/artifact/host-publisher.ts
|
|
168
|
+
init_esbuild_shims();
|
|
169
|
+
import { execFile } from "node:child_process";
|
|
170
|
+
import { promisify } from "node:util";
|
|
171
|
+
import { promises as fs2 } from "node:fs";
|
|
172
|
+
import os from "node:os";
|
|
173
|
+
import path3 from "node:path";
|
|
174
|
+
var execFileAsync = promisify(execFile);
|
|
175
|
+
var defaultRunCommand = /* @__PURE__ */ __name(async (command, args, signal) => {
|
|
176
|
+
await execFileAsync(command, args, { signal, maxBuffer: 10 * 1024 * 1024 });
|
|
177
|
+
}, "defaultRunCommand");
|
|
178
|
+
function tokenizeCommand(command) {
|
|
179
|
+
const tokens = [];
|
|
180
|
+
let current = "";
|
|
181
|
+
let started = false;
|
|
182
|
+
let quote;
|
|
183
|
+
for (const ch of command) {
|
|
184
|
+
if (quote) {
|
|
185
|
+
if (ch === quote) quote = void 0;
|
|
186
|
+
else current += ch;
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
if (ch === '"' || ch === "'") {
|
|
190
|
+
quote = ch;
|
|
191
|
+
started = true;
|
|
192
|
+
continue;
|
|
193
|
+
}
|
|
194
|
+
if (/\s/.test(ch)) {
|
|
195
|
+
if (started) {
|
|
196
|
+
tokens.push(current);
|
|
197
|
+
current = "";
|
|
198
|
+
started = false;
|
|
199
|
+
}
|
|
200
|
+
continue;
|
|
201
|
+
}
|
|
202
|
+
current += ch;
|
|
203
|
+
started = true;
|
|
204
|
+
}
|
|
205
|
+
if (quote) throw new Error("Unterminated quote in uploadCommand.");
|
|
206
|
+
if (started) tokens.push(current);
|
|
207
|
+
return tokens;
|
|
208
|
+
}
|
|
209
|
+
__name(tokenizeCommand, "tokenizeCommand");
|
|
210
|
+
function substitute(token, file, key) {
|
|
211
|
+
return token.split("{file}").join(file).split("{key}").join(key);
|
|
212
|
+
}
|
|
213
|
+
__name(substitute, "substitute");
|
|
214
|
+
function substituteKey(token, key) {
|
|
215
|
+
return token.split("{key}").join(key);
|
|
216
|
+
}
|
|
217
|
+
__name(substituteKey, "substituteKey");
|
|
218
|
+
function normalizeKeyPrefix(raw) {
|
|
219
|
+
const prefix = (raw || "artifacts").replace(/^\/+|\/+$/g, "");
|
|
220
|
+
if (!prefix) {
|
|
221
|
+
throw new Error(
|
|
222
|
+
'artifact.host.keyPrefix must not be empty or "/" after stripping slashes.'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (/[#?%\s]/.test(prefix)) {
|
|
226
|
+
throw new Error(
|
|
227
|
+
"artifact.host.keyPrefix must not contain #, ?, %, or whitespace."
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
return prefix;
|
|
231
|
+
}
|
|
232
|
+
__name(normalizeKeyPrefix, "normalizeKeyPrefix");
|
|
233
|
+
var HostPublisher = class {
|
|
234
|
+
constructor(config, run = defaultRunCommand) {
|
|
235
|
+
this.config = config;
|
|
236
|
+
this.run = run;
|
|
237
|
+
}
|
|
238
|
+
static {
|
|
239
|
+
__name(this, "HostPublisher");
|
|
240
|
+
}
|
|
241
|
+
kind = "host";
|
|
242
|
+
async publish(input, signal) {
|
|
243
|
+
const uploadCommand = this.config.uploadCommand?.trim();
|
|
244
|
+
const urlTemplate = this.config.urlTemplate?.trim();
|
|
245
|
+
if (!uploadCommand) {
|
|
246
|
+
throw new Error(
|
|
247
|
+
'artifact.host.uploadCommand is not configured (set it to e.g. "aws s3 cp {file} s3://bucket/{key}").'
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
if (!urlTemplate) {
|
|
251
|
+
throw new Error(
|
|
252
|
+
'artifact.host.urlTemplate is not configured (set it to e.g. "https://bucket.example.com/{key}").'
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
if (!uploadCommand.includes("{file}")) {
|
|
256
|
+
throw new Error(
|
|
257
|
+
"artifact.host.uploadCommand must include the {file} placeholder (the local HTML path to upload)."
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
if (!uploadCommand.includes("{key}")) {
|
|
261
|
+
throw new Error(
|
|
262
|
+
"artifact.host.uploadCommand must include the {key} placeholder so the upload destination matches the returned URL."
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (!urlTemplate.includes("{key}")) {
|
|
266
|
+
throw new Error(
|
|
267
|
+
"artifact.host.urlTemplate must include the {key} placeholder (the remote object key)."
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
if (urlTemplate.includes("{file}")) {
|
|
271
|
+
throw new Error(
|
|
272
|
+
"artifact.host.urlTemplate must not include {file}; only {key} is supported."
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
const prefix = normalizeKeyPrefix(this.config.keyPrefix);
|
|
276
|
+
const key = `${prefix}/${input.id}/index.html`;
|
|
277
|
+
const dir = await fs2.mkdtemp(path3.join(os.tmpdir(), "qwen-art-"));
|
|
278
|
+
const file = path3.join(dir, "index.html");
|
|
279
|
+
await fs2.writeFile(file, input.html, "utf8");
|
|
280
|
+
try {
|
|
281
|
+
const argv = tokenizeCommand(uploadCommand).map(
|
|
282
|
+
(t) => substitute(t, file, key)
|
|
283
|
+
);
|
|
284
|
+
const [command, ...args] = argv;
|
|
285
|
+
if (!command) {
|
|
286
|
+
throw new Error("artifact.host.uploadCommand is empty.");
|
|
287
|
+
}
|
|
288
|
+
await this.run(command, args, signal);
|
|
289
|
+
} finally {
|
|
290
|
+
await fs2.rm(dir, { recursive: true, force: true });
|
|
291
|
+
}
|
|
292
|
+
return { id: input.id, url: substituteKey(urlTemplate, key) };
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// packages/core/src/tools/artifact/oss-publisher.ts
|
|
297
|
+
init_esbuild_shims();
|
|
298
|
+
import { createHash as createHash2, createHmac } from "node:crypto";
|
|
299
|
+
var defaultHttpPut = /* @__PURE__ */ __name(async (url, headers, body, signal) => {
|
|
300
|
+
const timeout = AbortSignal.timeout(6e4);
|
|
301
|
+
const combinedSignal = signal ? AbortSignal.any([signal, timeout]) : timeout;
|
|
302
|
+
let res;
|
|
303
|
+
try {
|
|
304
|
+
res = await fetch(url, {
|
|
305
|
+
method: "PUT",
|
|
306
|
+
headers,
|
|
307
|
+
body,
|
|
308
|
+
signal: combinedSignal
|
|
309
|
+
});
|
|
310
|
+
} catch (err) {
|
|
311
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
312
|
+
throw new Error(`OSS upload to ${url} failed: ${message}`);
|
|
313
|
+
}
|
|
314
|
+
if (!res.ok) {
|
|
315
|
+
await res.body?.cancel().catch(() => {
|
|
316
|
+
});
|
|
317
|
+
throw new Error(`OSS upload failed: ${res.status} ${res.statusText}`);
|
|
318
|
+
}
|
|
319
|
+
await res.body?.cancel().catch(() => {
|
|
320
|
+
});
|
|
321
|
+
}, "defaultHttpPut");
|
|
322
|
+
function ossCredentialsFromEnv(env = process.env) {
|
|
323
|
+
const accessKeyId = env["OSS_ACCESS_KEY_ID"] || env["ALIBABA_CLOUD_ACCESS_KEY_ID"];
|
|
324
|
+
const accessKeySecret = env["OSS_ACCESS_KEY_SECRET"] || env["ALIBABA_CLOUD_ACCESS_KEY_SECRET"];
|
|
325
|
+
if (!accessKeyId || !accessKeySecret) return void 0;
|
|
326
|
+
const securityToken = env["OSS_SESSION_TOKEN"] || env["ALIBABA_CLOUD_SECURITY_TOKEN"] || void 0;
|
|
327
|
+
return { accessKeyId, accessKeySecret, securityToken };
|
|
328
|
+
}
|
|
329
|
+
__name(ossCredentialsFromEnv, "ossCredentialsFromEnv");
|
|
330
|
+
function signOssPut(params) {
|
|
331
|
+
const ossHeaders = {};
|
|
332
|
+
if (params.acl) ossHeaders["x-oss-object-acl"] = params.acl;
|
|
333
|
+
if (params.credentials.securityToken) {
|
|
334
|
+
ossHeaders["x-oss-security-token"] = params.credentials.securityToken;
|
|
335
|
+
}
|
|
336
|
+
const canonicalizedHeaders = Object.keys(ossHeaders).sort().map((k) => `${k}:${ossHeaders[k]}
|
|
337
|
+
`).join("");
|
|
338
|
+
const canonicalizedResource = `/${params.bucket}/${params.key}`;
|
|
339
|
+
const stringToSign = `PUT
|
|
340
|
+
${params.contentMd5}
|
|
341
|
+
${params.contentType}
|
|
342
|
+
${params.date}
|
|
343
|
+
${canonicalizedHeaders}${canonicalizedResource}`;
|
|
344
|
+
const signature = createHmac("sha1", params.credentials.accessKeySecret).update(stringToSign, "utf8").digest("base64");
|
|
345
|
+
return {
|
|
346
|
+
authorization: `OSS ${params.credentials.accessKeyId}:${signature}`,
|
|
347
|
+
ossHeaders
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
__name(signOssPut, "signOssPut");
|
|
351
|
+
var CONTENT_TYPE = "text/html";
|
|
352
|
+
function normalizeEndpoint(raw) {
|
|
353
|
+
const endpoint = raw.trim().replace(/^https?:\/\//, "").replace(/\/+$/, "");
|
|
354
|
+
if (!/^[a-z0-9.-]+\.aliyuncs\.com$/i.test(endpoint)) {
|
|
355
|
+
throw new Error(
|
|
356
|
+
`artifact.oss.endpoint does not look like a valid Aliyun OSS endpoint: ${endpoint}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return endpoint;
|
|
360
|
+
}
|
|
361
|
+
__name(normalizeEndpoint, "normalizeEndpoint");
|
|
362
|
+
function normalizeKeyPrefix2(raw) {
|
|
363
|
+
const prefix = (raw || "artifacts").replace(/^\/+|\/+$/g, "");
|
|
364
|
+
if (!prefix) {
|
|
365
|
+
throw new Error(
|
|
366
|
+
'artifact.oss.keyPrefix must not be empty or "/" after stripping slashes.'
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
if (/[#?%\s]/.test(prefix)) {
|
|
370
|
+
throw new Error(
|
|
371
|
+
"artifact.oss.keyPrefix must not contain #, ?, %, or whitespace."
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return prefix;
|
|
375
|
+
}
|
|
376
|
+
__name(normalizeKeyPrefix2, "normalizeKeyPrefix");
|
|
377
|
+
function normalizePublicBaseUrl(raw) {
|
|
378
|
+
const base = raw?.trim().replace(/\/+$/, "");
|
|
379
|
+
if (!base) return void 0;
|
|
380
|
+
if (!/^https?:\/\//i.test(base)) {
|
|
381
|
+
throw new Error(
|
|
382
|
+
"artifact.oss.publicBaseUrl must start with http:// or https://."
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
return base;
|
|
386
|
+
}
|
|
387
|
+
__name(normalizePublicBaseUrl, "normalizePublicBaseUrl");
|
|
388
|
+
var OssPublisher = class {
|
|
389
|
+
constructor(config, deps = {}) {
|
|
390
|
+
this.config = config;
|
|
391
|
+
this.deps = deps;
|
|
392
|
+
}
|
|
393
|
+
static {
|
|
394
|
+
__name(this, "OssPublisher");
|
|
395
|
+
}
|
|
396
|
+
kind = "oss";
|
|
397
|
+
async publish(input, signal) {
|
|
398
|
+
const bucket = this.config.bucket?.trim();
|
|
399
|
+
const rawEndpoint = this.config.endpoint?.trim();
|
|
400
|
+
if (!bucket) {
|
|
401
|
+
throw new Error("artifact.oss.bucket is not configured.");
|
|
402
|
+
}
|
|
403
|
+
if (!rawEndpoint) {
|
|
404
|
+
throw new Error(
|
|
405
|
+
'artifact.oss.endpoint is not configured (e.g. "oss-cn-hangzhou.aliyuncs.com").'
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
const endpoint = normalizeEndpoint(rawEndpoint);
|
|
409
|
+
const credentials = (this.deps.credentials ?? ossCredentialsFromEnv)();
|
|
410
|
+
if (!credentials) {
|
|
411
|
+
throw new Error(
|
|
412
|
+
"OSS credentials not found. Set OSS_ACCESS_KEY_ID and OSS_ACCESS_KEY_SECRET (or ALIBABA_CLOUD_ACCESS_KEY_ID / ALIBABA_CLOUD_ACCESS_KEY_SECRET)."
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
const prefix = normalizeKeyPrefix2(this.config.keyPrefix);
|
|
416
|
+
const key = `${prefix}/${input.id}/index.html`;
|
|
417
|
+
const base = normalizePublicBaseUrl(this.config.publicBaseUrl);
|
|
418
|
+
const acl = this.config.acl ?? "public-read";
|
|
419
|
+
const date = (this.deps.now ? this.deps.now() : /* @__PURE__ */ new Date()).toUTCString();
|
|
420
|
+
const contentMd5 = createHash2("md5").update(input.html, "utf8").digest("base64");
|
|
421
|
+
const { authorization, ossHeaders } = signOssPut({
|
|
422
|
+
credentials,
|
|
423
|
+
bucket,
|
|
424
|
+
key,
|
|
425
|
+
contentMd5,
|
|
426
|
+
contentType: CONTENT_TYPE,
|
|
427
|
+
date,
|
|
428
|
+
acl
|
|
429
|
+
});
|
|
430
|
+
const putUrl = `https://${bucket}.${endpoint}/${key}`;
|
|
431
|
+
const httpPut = this.deps.httpPut ?? defaultHttpPut;
|
|
432
|
+
await httpPut(
|
|
433
|
+
putUrl,
|
|
434
|
+
{
|
|
435
|
+
Date: date,
|
|
436
|
+
"Content-MD5": contentMd5,
|
|
437
|
+
"Content-Type": CONTENT_TYPE,
|
|
438
|
+
Authorization: authorization,
|
|
439
|
+
...ossHeaders
|
|
440
|
+
},
|
|
441
|
+
input.html,
|
|
442
|
+
signal
|
|
443
|
+
);
|
|
444
|
+
const url = base ? `${base}/${key}` : putUrl;
|
|
445
|
+
return { id: input.id, url };
|
|
446
|
+
}
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
// packages/core/src/tools/artifact/create-publisher.ts
|
|
450
|
+
function createArtifactPublisher(config) {
|
|
451
|
+
const kind = config.getArtifactPublisherKind();
|
|
452
|
+
switch (kind) {
|
|
453
|
+
case "host":
|
|
454
|
+
return new HostPublisher(
|
|
455
|
+
config.getArtifactHostConfig() ?? {
|
|
456
|
+
uploadCommand: "",
|
|
457
|
+
urlTemplate: ""
|
|
458
|
+
}
|
|
459
|
+
);
|
|
460
|
+
case "oss":
|
|
461
|
+
return new OssPublisher(
|
|
462
|
+
config.getArtifactOssConfig() ?? { bucket: "", endpoint: "" }
|
|
463
|
+
);
|
|
464
|
+
case "local":
|
|
465
|
+
return new LocalPublisher();
|
|
466
|
+
default: {
|
|
467
|
+
const unknown = kind;
|
|
468
|
+
throw new Error(`Unknown artifact publisher kind: ${unknown}`);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
__name(createArtifactPublisher, "createArtifactPublisher");
|
|
473
|
+
|
|
474
|
+
// packages/core/src/tools/artifact/artifact-tool.ts
|
|
475
|
+
var DESCRIPTION = `Publishes a self-contained HTML page as an interactive Artifact, optionally opens it in the browser depending on settings, and returns a shareable link when a remote host is configured. Use it to turn session output into a durable, interactive page \u2014 a PR walkthrough, an architecture tour, a project dashboard.
|
|
476
|
+
|
|
477
|
+
Workflow:
|
|
478
|
+
- Write the page to a file first (via Write/Edit), then call Artifact with that file's absolute path.
|
|
479
|
+
- Write a BODY-ONLY fragment: no <!doctype>, <html>, <head>, or <body> tags \u2014 they are added at publish time, along with a minimal CSS reset.
|
|
480
|
+
- Self-contained only: inline all CSS and JS; embed images/fonts as data: URIs. No external scripts, stylesheets, fonts, or remote images.
|
|
481
|
+
- Responsive: relative units, flex/grid, max-width:100% on media; wide content (tables, diagrams, code) scrolls inside its own overflow-x:auto container.
|
|
482
|
+
- Set a concise \`title\` \u2014 it names the browser tab.
|
|
483
|
+
|
|
484
|
+
To update an artifact, call Artifact again with the SAME file path: it redeploys to the same URL. A different path creates a separate Artifact.
|
|
485
|
+
|
|
486
|
+
Set artifact.autoOpen=false in settings.json, or QWEN_ARTIFACT_NO_AUTO_OPEN=1, to publish without launching a browser.`;
|
|
487
|
+
var debugLogger = createDebugLogger("artifact");
|
|
488
|
+
var ArtifactToolInvocation = class extends BaseToolInvocation {
|
|
489
|
+
constructor(config, publisher, openUrl, params) {
|
|
490
|
+
super(params);
|
|
491
|
+
this.config = config;
|
|
492
|
+
this.publisher = publisher;
|
|
493
|
+
this.openUrl = openUrl;
|
|
494
|
+
this.shouldAutoOpen = config.shouldAutoOpenArtifact();
|
|
495
|
+
}
|
|
496
|
+
static {
|
|
497
|
+
__name(this, "ArtifactToolInvocation");
|
|
498
|
+
}
|
|
499
|
+
shouldAutoOpen;
|
|
500
|
+
getDescription() {
|
|
501
|
+
const relativePath = makeRelative(
|
|
502
|
+
this.params.file_path,
|
|
503
|
+
this.config.getTargetDir()
|
|
504
|
+
);
|
|
505
|
+
return `Publishing artifact from ${shortenPath(relativePath)}`;
|
|
506
|
+
}
|
|
507
|
+
/** Publishing writes outside the project and may open a browser — always ask. */
|
|
508
|
+
getDefaultPermission() {
|
|
509
|
+
return Promise.resolve("ask");
|
|
510
|
+
}
|
|
511
|
+
getConfirmationDetails(_abortSignal) {
|
|
512
|
+
const relativePath = makeRelative(
|
|
513
|
+
this.params.file_path,
|
|
514
|
+
this.config.getTargetDir()
|
|
515
|
+
);
|
|
516
|
+
const backendLabel = this.publisher.kind === "host" ? "custom upload" : this.publisher.kind;
|
|
517
|
+
const openSuffix = this.shouldAutoOpen ? " and open it in your browser" : "";
|
|
518
|
+
const remoteOpenSuffix = this.shouldAutoOpen ? " and opens the shareable link in your browser" : "";
|
|
519
|
+
const prompt = this.publisher.kind === "local" ? `Publish ${shortenPath(relativePath)} as an interactive Artifact${openSuffix}.` : `Publish ${shortenPath(relativePath)} as an interactive Artifact. This uploads the page to a remote host (${backendLabel})${remoteOpenSuffix}.`;
|
|
520
|
+
const details = {
|
|
521
|
+
type: "info",
|
|
522
|
+
title: "Publish Artifact",
|
|
523
|
+
prompt,
|
|
524
|
+
onConfirm: /* @__PURE__ */ __name(async () => {
|
|
525
|
+
}, "onConfirm")
|
|
526
|
+
};
|
|
527
|
+
return Promise.resolve(details);
|
|
528
|
+
}
|
|
529
|
+
async execute(signal) {
|
|
530
|
+
const { file_path } = this.params;
|
|
531
|
+
let fragment;
|
|
532
|
+
try {
|
|
533
|
+
const { content } = await this.config.getFileSystemService().readTextFile({ path: file_path });
|
|
534
|
+
fragment = content;
|
|
535
|
+
} catch (err) {
|
|
536
|
+
const notFound = isNodeError(err) && err.code === "ENOENT";
|
|
537
|
+
const message = notFound ? `Artifact source file not found: ${file_path}. Write the page content to this file first.` : `Error reading artifact source file '${file_path}': ${getErrorMessage(err)}`;
|
|
538
|
+
return {
|
|
539
|
+
llmContent: message,
|
|
540
|
+
returnDisplay: message,
|
|
541
|
+
error: {
|
|
542
|
+
message,
|
|
543
|
+
type: notFound ? "file_not_found" /* FILE_NOT_FOUND */ : "read_content_failure" /* READ_CONTENT_FAILURE */
|
|
544
|
+
}
|
|
545
|
+
};
|
|
546
|
+
}
|
|
547
|
+
const contentError = validateSelfContained(fragment);
|
|
548
|
+
if (contentError) {
|
|
549
|
+
return {
|
|
550
|
+
llmContent: contentError,
|
|
551
|
+
returnDisplay: contentError,
|
|
552
|
+
error: { message: contentError, type: "execution_failed" /* EXECUTION_FAILED */ }
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
const title = sanitizeArtifactTitle(
|
|
556
|
+
this.params.title ?? path4.basename(file_path).replace(/\.html?$/i, "")
|
|
557
|
+
);
|
|
558
|
+
const html = wrapArtifactHtml(fragment, title);
|
|
559
|
+
const bytes = byteLength(html);
|
|
560
|
+
if (bytes > MAX_ARTIFACT_BYTES) {
|
|
561
|
+
const message = `Artifact is too large (${bytes} bytes > ${MAX_ARTIFACT_BYTES} byte limit). Trim the content or split it across multiple artifacts.`;
|
|
562
|
+
return {
|
|
563
|
+
llmContent: message,
|
|
564
|
+
returnDisplay: message,
|
|
565
|
+
error: { message, type: "file_too_large" /* FILE_TOO_LARGE */ }
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
let url;
|
|
569
|
+
let filePath;
|
|
570
|
+
try {
|
|
571
|
+
const published = await this.publisher.publish(
|
|
572
|
+
{ id: artifactIdFromPath(file_path), title, html },
|
|
573
|
+
signal
|
|
574
|
+
);
|
|
575
|
+
url = published.url;
|
|
576
|
+
filePath = published.filePath;
|
|
577
|
+
} catch (err) {
|
|
578
|
+
if (signal.aborted || isAbortError(err)) {
|
|
579
|
+
const message2 = "Artifact publishing was cancelled.";
|
|
580
|
+
return {
|
|
581
|
+
llmContent: message2,
|
|
582
|
+
returnDisplay: message2
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
const message = `Failed to publish artifact: ${getErrorMessage(err)}`;
|
|
586
|
+
return {
|
|
587
|
+
llmContent: message,
|
|
588
|
+
returnDisplay: message,
|
|
589
|
+
error: { message, type: "execution_failed" /* EXECUTION_FAILED */ }
|
|
590
|
+
};
|
|
591
|
+
}
|
|
592
|
+
if (this.shouldAutoOpen) {
|
|
593
|
+
try {
|
|
594
|
+
await this.openUrl(url, {
|
|
595
|
+
allowFile: true,
|
|
596
|
+
allowedFilePaths: filePath ? [filePath] : []
|
|
597
|
+
});
|
|
598
|
+
} catch (err) {
|
|
599
|
+
debugLogger.warn(
|
|
600
|
+
`Failed to open browser for artifact "${title}": ${getErrorMessage(err)}`
|
|
601
|
+
);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
const llmContent = `Published artifact "${title}" to ${url}. Share or open this URL to view the interactive page. Re-run Artifact with the same file path to update it.`;
|
|
605
|
+
return {
|
|
606
|
+
llmContent,
|
|
607
|
+
returnDisplay: `\u{1F4C4} Published artifact **${title}**
|
|
608
|
+
|
|
609
|
+
${url}`,
|
|
610
|
+
resultFilePaths: filePath ? [filePath] : void 0
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
};
|
|
614
|
+
var ArtifactTool = class _ArtifactTool extends BaseDeclarativeTool {
|
|
615
|
+
constructor(config, publisher, openUrl = openBrowserSecurely) {
|
|
616
|
+
super(
|
|
617
|
+
_ArtifactTool.Name,
|
|
618
|
+
ToolDisplayNames.ARTIFACT,
|
|
619
|
+
DESCRIPTION,
|
|
620
|
+
"other" /* Other */,
|
|
621
|
+
{
|
|
622
|
+
type: "object",
|
|
623
|
+
properties: {
|
|
624
|
+
file_path: {
|
|
625
|
+
type: "string",
|
|
626
|
+
description: "Absolute path to the body-only HTML fragment file to publish."
|
|
627
|
+
},
|
|
628
|
+
title: {
|
|
629
|
+
type: "string",
|
|
630
|
+
description: "Concise title for the artifact (names the browser tab and listing)."
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
required: ["file_path"]
|
|
634
|
+
}
|
|
635
|
+
);
|
|
636
|
+
this.config = config;
|
|
637
|
+
this.openUrl = openUrl;
|
|
638
|
+
this.publisher = publisher ?? createArtifactPublisher(config);
|
|
639
|
+
}
|
|
640
|
+
static {
|
|
641
|
+
__name(this, "ArtifactTool");
|
|
642
|
+
}
|
|
643
|
+
static Name = ToolNames.ARTIFACT;
|
|
644
|
+
publisher;
|
|
645
|
+
validateToolParamValues(params) {
|
|
646
|
+
const filePath = unescapePath((params.file_path ?? "").trim());
|
|
647
|
+
params.file_path = filePath;
|
|
648
|
+
if (!filePath) {
|
|
649
|
+
return 'Missing or empty "file_path"';
|
|
650
|
+
}
|
|
651
|
+
if (!path4.isAbsolute(filePath)) {
|
|
652
|
+
return `File path must be absolute: ${filePath}`;
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
toAutoClassifierInput(params) {
|
|
657
|
+
return { file_path: params.file_path, title: params.title };
|
|
658
|
+
}
|
|
659
|
+
createInvocation(params) {
|
|
660
|
+
return new ArtifactToolInvocation(
|
|
661
|
+
this.config,
|
|
662
|
+
this.publisher,
|
|
663
|
+
this.openUrl,
|
|
664
|
+
params
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
};
|
|
668
|
+
export {
|
|
669
|
+
ArtifactTool
|
|
670
|
+
};
|
|
671
|
+
/**
|
|
672
|
+
* @license
|
|
673
|
+
* Copyright 2025 Google LLC
|
|
674
|
+
* SPDX-License-Identifier: Apache-2.0
|
|
675
|
+
*/
|