mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 19:05:11 +00:00
Fix lints.
This commit is contained in:
parent
6d046236bf
commit
99983af597
27 changed files with 855 additions and 1060 deletions
11
biome.json
11
biome.json
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"$schema": "https://biomejs.dev/schemas/2.1.4/schema.json",
|
||||
"$schema": "https://biomejs.dev/schemas/2.2.5/schema.json",
|
||||
"linter": {
|
||||
"enabled": true,
|
||||
"rules": {
|
||||
|
|
@ -28,7 +28,14 @@
|
|||
"lineWidth": 120
|
||||
},
|
||||
"files": {
|
||||
"includes": ["packages/*/src/**/*", "packages/*/test/**/*", "*.json", "*.md", "!**/node_modules/**/*"],
|
||||
"includes": [
|
||||
"packages/*/src/**/*",
|
||||
"packages/*/test/**/*",
|
||||
"*.json",
|
||||
"*.md",
|
||||
"!**/node_modules/**/*",
|
||||
"!**/test-sessions.ts"
|
||||
],
|
||||
"experimentalScannerIgnores": ["node_modules"]
|
||||
}
|
||||
}
|
||||
|
|
|
|||
1016
package-lock.json
generated
1016
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -25,7 +25,7 @@
|
|||
"ajv": "^8.17.1",
|
||||
"ajv-formats": "^3.0.1",
|
||||
"chalk": "^5.6.2",
|
||||
"openai": "^5.20.0",
|
||||
"openai": "5.21.0",
|
||||
"partial-json": "^0.1.7",
|
||||
"zod-to-json-schema": "^3.24.6"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ export const MODELS = {
|
|||
cacheRead: 0.3,
|
||||
cacheWrite: 3.75,
|
||||
},
|
||||
contextWindow: 1000000,
|
||||
contextWindow: 200000,
|
||||
maxTokens: 64000,
|
||||
} satisfies Model<"anthropic-messages">,
|
||||
"claude-sonnet-4-20250514": {
|
||||
|
|
@ -1660,13 +1660,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.6,
|
||||
output: 2,
|
||||
input: 0.5,
|
||||
output: 1.75,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 202752,
|
||||
maxTokens: 4096,
|
||||
maxTokens: 202752,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"deepseek/deepseek-v3.2-exp": {
|
||||
id: "deepseek/deepseek-v3.2-exp",
|
||||
|
|
@ -1694,13 +1694,13 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text", "image"],
|
||||
cost: {
|
||||
input: 0.3,
|
||||
output: 3,
|
||||
input: 0.5,
|
||||
output: 3.5,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 32768,
|
||||
contextWindow: 65536,
|
||||
maxTokens: 65536,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"qwen/qwen3-vl-235b-a22b-instruct": {
|
||||
id: "qwen/qwen3-vl-235b-a22b-instruct",
|
||||
|
|
@ -1770,6 +1770,23 @@ export const MODELS = {
|
|||
contextWindow: 163840,
|
||||
maxTokens: 163840,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"alibaba/tongyi-deepresearch-30b-a3b:free": {
|
||||
id: "alibaba/tongyi-deepresearch-30b-a3b:free",
|
||||
name: "Tongyi DeepResearch 30B A3B (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 131072,
|
||||
maxTokens: 131072,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"alibaba/tongyi-deepresearch-30b-a3b": {
|
||||
id: "alibaba/tongyi-deepresearch-30b-a3b",
|
||||
name: "Tongyi DeepResearch 30B A3B",
|
||||
|
|
@ -1779,8 +1796,8 @@ export const MODELS = {
|
|||
reasoning: true,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0,
|
||||
output: 0,
|
||||
input: 0.09,
|
||||
output: 0.39999999999999997,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
|
|
@ -1838,9 +1855,9 @@ export const MODELS = {
|
|||
contextWindow: 262144,
|
||||
maxTokens: 262144,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meituan/longcat-flash-chat": {
|
||||
id: "meituan/longcat-flash-chat",
|
||||
name: "Meituan: LongCat Flash Chat",
|
||||
"meituan/longcat-flash-chat:free": {
|
||||
id: "meituan/longcat-flash-chat:free",
|
||||
name: "Meituan: LongCat Flash Chat (free)",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
|
|
@ -3351,23 +3368,6 @@ export const MODELS = {
|
|||
contextWindow: 65000,
|
||||
maxTokens: 4096,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-8b-instruct": {
|
||||
id: "meta-llama/llama-3.1-8b-instruct",
|
||||
name: "Meta: Llama 3.1 8B Instruct",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.02,
|
||||
output: 0.03,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 16384,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-405b-instruct": {
|
||||
id: "meta-llama/llama-3.1-405b-instruct",
|
||||
name: "Meta: Llama 3.1 405B Instruct",
|
||||
|
|
@ -3385,6 +3385,23 @@ export const MODELS = {
|
|||
contextWindow: 32768,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-8b-instruct": {
|
||||
id: "meta-llama/llama-3.1-8b-instruct",
|
||||
name: "Meta: Llama 3.1 8B Instruct",
|
||||
api: "openai-completions",
|
||||
provider: "openrouter",
|
||||
baseUrl: "https://openrouter.ai/api/v1",
|
||||
reasoning: false,
|
||||
input: ["text"],
|
||||
cost: {
|
||||
input: 0.02,
|
||||
output: 0.03,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
},
|
||||
contextWindow: 16384,
|
||||
maxTokens: 16384,
|
||||
} satisfies Model<"openai-completions">,
|
||||
"meta-llama/llama-3.1-70b-instruct": {
|
||||
id: "meta-llama/llama-3.1-70b-instruct",
|
||||
name: "Meta: Llama 3.1 70B Instruct",
|
||||
|
|
|
|||
|
|
@ -433,8 +433,8 @@ function mapStopReason(reason: FinishReason): StopReason {
|
|||
case FinishReason.SPII:
|
||||
case FinishReason.SAFETY:
|
||||
case FinishReason.IMAGE_SAFETY:
|
||||
case FinishReason.IMAGE_PROHIBITED_CONTENT:
|
||||
case FinishReason.RECITATION:
|
||||
return "error";
|
||||
case FinishReason.FINISH_REASON_UNSPECIFIED:
|
||||
case FinishReason.OTHER:
|
||||
case FinishReason.LANGUAGE:
|
||||
|
|
|
|||
|
|
@ -34,6 +34,6 @@
|
|||
},
|
||||
"content_security_policy": {
|
||||
"extension_pages": "script-src 'self'; object-src 'self'",
|
||||
"sandbox": "sandbox allow-scripts allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http:; script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https: http:; connect-src * data: blob:; img-src * data: blob:; style-src 'self' 'unsafe-inline' https: http:; font-src * data:; worker-src blob:; child-src blob:; object-src 'none'"
|
||||
"sandbox": "sandbox allow-scripts allow-modals; script-src 'self' 'unsafe-inline' 'unsafe-eval' https: http:; script-src-elem 'self' 'unsafe-inline' 'unsafe-eval' https: http:; connect-src * data: blob:; img-src * data: blob:; style-src 'self' 'unsafe-inline' https: http:; font-src * data:; worker-src blob:; child-src blob: https: http:; object-src 'none'"
|
||||
}
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@
|
|||
"check": "npm run typecheck"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mariozechner/mini-lit": "^0.1.4",
|
||||
"@mariozechner/mini-lit": "^0.1.7",
|
||||
"@mariozechner/pi-ai": "^0.5.43",
|
||||
"docx-preview": "^0.3.7",
|
||||
"js-interpreter": "^6.0.1",
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { browserJavaScriptTool, createJavaScriptReplTool } from "./tools/index.j
|
|||
import { registerToolRenderer } from "./tools/renderer-registry.js";
|
||||
import { getAuthToken } from "./utils/auth-token.js";
|
||||
import { i18n } from "./utils/i18n.js";
|
||||
import { longSession, simpleHtml } from "./utils/test-sessions.js";
|
||||
|
||||
const BREAKPOINT = 800; // px - switch between overlay and side-by-side
|
||||
|
||||
|
|
@ -100,15 +99,15 @@ export class ChatPanel extends LitElement {
|
|||
this.requestUpdate();
|
||||
};
|
||||
|
||||
let initialState = {
|
||||
const initialState = {
|
||||
systemPrompt: this.systemPrompt,
|
||||
model: getModel("anthropic", "claude-3-5-haiku-20241022"),
|
||||
model: getModel("anthropic", "claude-sonnet-4-5-20250929"),
|
||||
tools: [browserJavaScriptTool, javascriptReplTool, this.artifactsPanel.tool],
|
||||
thinkingLevel: "off" as ThinkingLevel,
|
||||
messages: [],
|
||||
} satisfies Partial<AgentSessionState>;
|
||||
// initialState = { ...initialState, ...(simpleHtml as any) };
|
||||
initialState = { ...initialState, ...(longSession as any) };
|
||||
// initialState = { ...initialState, ...(longSession as any) };
|
||||
|
||||
// Create agent session first so attachments provider works
|
||||
this.session = new AgentSession({
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
@import "@mariozechner/mini-lit/styles/themes/default.css";
|
||||
|
||||
/* Tell Tailwind to scan mini-lit components */
|
||||
/* biome-ignore lint/suspicious/noUnknownAtRules: Tailwind 4 source directive */
|
||||
@source "../../../node_modules/@mariozechner/mini-lit/dist";
|
||||
|
||||
/* Import Tailwind */
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
import { html } from "@mariozechner/mini-lit";
|
||||
import { LitElement } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
|
||||
export class ModeToggle extends LitElement {
|
||||
@property({ type: Array }) modes: string[] = ["Mode 1", "Mode 2"];
|
||||
@property({ type: Number }) selectedIndex = 0;
|
||||
|
||||
protected override createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
private setMode(index: number) {
|
||||
if (this.selectedIndex !== index && index >= 0 && index < this.modes.length) {
|
||||
this.selectedIndex = index;
|
||||
this.dispatchEvent(
|
||||
new CustomEvent<{ index: number; mode: string }>("mode-change", {
|
||||
detail: { index, mode: this.modes[index] },
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
override render() {
|
||||
if (this.modes.length < 2) return html``;
|
||||
|
||||
return html`
|
||||
<div class="inline-flex items-center h-7 rounded-md overflow-hidden border border-border bg-muted/60">
|
||||
${this.modes.map(
|
||||
(mode, index) => html`
|
||||
<button
|
||||
class="px-3 h-full flex items-center text-sm font-medium transition-colors ${
|
||||
index === this.selectedIndex
|
||||
? "bg-card text-foreground shadow-sm"
|
||||
: "text-muted-foreground hover:text-accent-foreground"
|
||||
}"
|
||||
@click=${() => this.setMode(index)}
|
||||
title="${mode}"
|
||||
>
|
||||
${mode}
|
||||
</button>
|
||||
`,
|
||||
)}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element only once
|
||||
if (!customElements.get("mode-toggle")) {
|
||||
customElements.define("mode-toggle", ModeToggle);
|
||||
}
|
||||
|
|
@ -2,7 +2,6 @@ import { LitElement } from "lit";
|
|||
import { customElement } from "lit/decorators.js";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
|
||||
// @ts-ignore - browser global exists in Firefox
|
||||
declare const browser: any;
|
||||
|
||||
export interface SandboxFile {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
import { Alert, Badge, Button, DialogHeader, html, type TemplateResult } from "@mariozechner/mini-lit";
|
||||
import { Alert, Badge, Button, DialogBase, DialogHeader, html, type TemplateResult } from "@mariozechner/mini-lit";
|
||||
import { type Context, complete, getModel, getProviders } from "@mariozechner/pi-ai";
|
||||
import type { PropertyValues } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { Input } from "../components/Input.js";
|
||||
import { keyStore } from "../state/KeyStore.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import { DialogBase } from "./DialogBase.js";
|
||||
|
||||
// Test models for each provider - known to be reliable and cheap
|
||||
const TEST_MODELS: Record<string, string> = {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import { Button, html, icon } from "@mariozechner/mini-lit";
|
||||
import "@mariozechner/mini-lit/dist/ModeToggle.js";
|
||||
import { renderAsync } from "docx-preview";
|
||||
import { LitElement } from "lit";
|
||||
import { state } from "lit/decorators.js";
|
||||
import { Download, X } from "lucide";
|
||||
import * as pdfjsLib from "pdfjs-dist";
|
||||
import * as XLSX from "xlsx";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import "../components/ModeToggle.js";
|
||||
import type { Attachment } from "../utils/attachment-utils.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
|
||||
type FileType = "image" | "pdf" | "docx" | "pptx" | "excel" | "text";
|
||||
|
||||
|
|
|
|||
|
|
@ -1,64 +0,0 @@
|
|||
import { Dialog } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { LitElement, type TemplateResult } from "lit";
|
||||
|
||||
export abstract class DialogBase extends LitElement {
|
||||
// Modal configuration - can be overridden by subclasses
|
||||
protected modalWidth = "min(600px, 90vw)";
|
||||
protected modalHeight = "min(600px, 80vh)";
|
||||
private boundHandleKeyDown?: (e: KeyboardEvent) => void;
|
||||
private previousFocus?: HTMLElement;
|
||||
|
||||
protected override createRenderRoot(): HTMLElement | DocumentFragment {
|
||||
return this;
|
||||
}
|
||||
|
||||
open() {
|
||||
// Store the currently focused element
|
||||
this.previousFocus = document.activeElement as HTMLElement;
|
||||
|
||||
document.body.appendChild(this);
|
||||
this.boundHandleKeyDown = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
window.addEventListener("keydown", this.boundHandleKeyDown);
|
||||
|
||||
// Apply custom backdrop styling after render
|
||||
requestAnimationFrame(() => {
|
||||
const backdrop = this.querySelector(".fixed.inset-0");
|
||||
if (backdrop instanceof HTMLElement) {
|
||||
backdrop.classList.remove("bg-black/50");
|
||||
backdrop.classList.add("bg-background/80", "backdrop-blur-sm");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
if (this.boundHandleKeyDown) {
|
||||
window.removeEventListener("keydown", this.boundHandleKeyDown);
|
||||
}
|
||||
this.remove();
|
||||
|
||||
// Restore focus to the previously focused element
|
||||
if (this.previousFocus?.focus) {
|
||||
// Use requestAnimationFrame to ensure the dialog is fully removed first
|
||||
requestAnimationFrame(() => {
|
||||
this.previousFocus?.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Abstract method that subclasses must implement
|
||||
protected abstract renderContent(): TemplateResult;
|
||||
|
||||
override render() {
|
||||
return Dialog({
|
||||
isOpen: true,
|
||||
onClose: () => this.close(),
|
||||
width: this.modalWidth,
|
||||
height: this.modalHeight,
|
||||
children: this.renderContent(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { Badge, Button, DialogHeader, html, icon, type TemplateResult } from "@mariozechner/mini-lit";
|
||||
import { Badge, Button, DialogBase, DialogHeader, html, icon, type TemplateResult } from "@mariozechner/mini-lit";
|
||||
import type { Model } from "@mariozechner/pi-ai";
|
||||
import { MODELS } from "@mariozechner/pi-ai/dist/models.generated.js";
|
||||
import type { PropertyValues } from "lit";
|
||||
|
|
@ -9,7 +9,6 @@ import { Ollama } from "ollama/dist/browser.mjs";
|
|||
import { Input } from "../components/Input.js";
|
||||
import { formatModelCost } from "../utils/format.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import { DialogBase } from "./DialogBase.js";
|
||||
|
||||
@customElement("agent-model-selector")
|
||||
export class ModelSelector extends DialogBase {
|
||||
|
|
|
|||
|
|
@ -1,99 +0,0 @@
|
|||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { DialogContent, DialogFooter, DialogHeader } from "@mariozechner/mini-lit/dist/Dialog.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
import { html, type PropertyValues, type TemplateResult } from "lit";
|
||||
import { customElement } from "lit/decorators/custom-element.js";
|
||||
import { property } from "lit/decorators/property.js";
|
||||
import { state } from "lit/decorators/state.js";
|
||||
import { createRef } from "lit/directives/ref.js";
|
||||
import { i18n } from "../utils/i18n.js";
|
||||
import { DialogBase } from "./DialogBase.js";
|
||||
|
||||
@customElement("prompt-dialog")
|
||||
export class PromptDialog extends DialogBase {
|
||||
@property() headerTitle = "";
|
||||
@property() message = "";
|
||||
@property() defaultValue = "";
|
||||
@property() isPassword = false;
|
||||
|
||||
@state() private inputValue = "";
|
||||
private resolvePromise?: (value: string | undefined) => void;
|
||||
private inputRef = createRef<HTMLInputElement>();
|
||||
|
||||
protected override modalWidth = "min(400px, 90vw)";
|
||||
protected override modalHeight = "auto";
|
||||
|
||||
static async ask(
|
||||
title: string,
|
||||
message: string,
|
||||
defaultValue = "",
|
||||
isPassword = false,
|
||||
): Promise<string | undefined> {
|
||||
const dialog = new PromptDialog();
|
||||
dialog.headerTitle = title;
|
||||
dialog.message = message;
|
||||
dialog.defaultValue = defaultValue;
|
||||
dialog.isPassword = isPassword;
|
||||
dialog.inputValue = defaultValue;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
dialog.resolvePromise = resolve;
|
||||
dialog.open();
|
||||
});
|
||||
}
|
||||
|
||||
protected override firstUpdated(_changedProperties: PropertyValues): void {
|
||||
super.firstUpdated(_changedProperties);
|
||||
this.inputRef.value?.focus();
|
||||
}
|
||||
|
||||
private handleConfirm() {
|
||||
this.resolvePromise?.(this.inputValue);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private handleCancel() {
|
||||
this.resolvePromise?.(undefined);
|
||||
this.close();
|
||||
}
|
||||
|
||||
protected override renderContent(): TemplateResult {
|
||||
return DialogContent({
|
||||
children: html`
|
||||
${DialogHeader({
|
||||
title: this.headerTitle || i18n("Input Required"),
|
||||
description: this.message,
|
||||
})}
|
||||
${Input({
|
||||
type: this.isPassword ? "password" : "text",
|
||||
value: this.inputValue,
|
||||
className: "w-full",
|
||||
inputRef: this.inputRef,
|
||||
onInput: (e: Event) => {
|
||||
this.inputValue = (e.target as HTMLInputElement).value;
|
||||
},
|
||||
onKeyDown: (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter") this.handleConfirm();
|
||||
if (e.key === "Escape") this.handleCancel();
|
||||
},
|
||||
})}
|
||||
${DialogFooter({
|
||||
children: html`
|
||||
${Button({
|
||||
variant: "outline",
|
||||
onClick: () => this.handleCancel(),
|
||||
children: i18n("Cancel"),
|
||||
})}
|
||||
${Button({
|
||||
variant: "default",
|
||||
onClick: () => this.handleConfirm(),
|
||||
children: i18n("Confirm"),
|
||||
})}
|
||||
`,
|
||||
})}
|
||||
`,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default PromptDialog;
|
||||
|
|
@ -2,12 +2,11 @@ import { Button, icon } from "@mariozechner/mini-lit";
|
|||
import "@mariozechner/mini-lit/dist/ThemeToggle.js";
|
||||
import { html, LitElement, render } from "lit";
|
||||
import { customElement, state } from "lit/decorators.js";
|
||||
import { RefreshCw, Settings } from "lucide";
|
||||
import { Plus, RefreshCw, Settings } from "lucide";
|
||||
import "./ChatPanel.js";
|
||||
import "./components/SandboxedIframe.js";
|
||||
import { ApiKeysDialog } from "./dialogs/ApiKeysDialog.js";
|
||||
import "./utils/live-reload.js";
|
||||
import { SandboxIframe } from "./components/SandboxedIframe.js";
|
||||
import "./components/SandboxedIframe.js";
|
||||
|
||||
async function getDom() {
|
||||
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
||||
|
|
@ -19,187 +18,10 @@ async function getDom() {
|
|||
});
|
||||
}
|
||||
|
||||
@customElement("sandbox-test")
|
||||
export class SandboxTest extends LitElement {
|
||||
@state() private result = "";
|
||||
@state() private testing = false;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
private async testREPL() {
|
||||
this.testing = true;
|
||||
this.result = "Testing REPL...";
|
||||
|
||||
const sandbox = new SandboxIframe();
|
||||
sandbox.style.display = "none";
|
||||
this.appendChild(sandbox);
|
||||
|
||||
try {
|
||||
const result = await sandbox.execute(
|
||||
"test-repl",
|
||||
`
|
||||
console.log("Hello from REPL!");
|
||||
console.log("Testing math:", 2 + 2);
|
||||
await returnFile("test.txt", "Hello World", "text/plain");
|
||||
`,
|
||||
[],
|
||||
);
|
||||
|
||||
this.result = `✓ REPL Test Success!\n\nConsole:\n${result.console.map((l: { type: string; text: string }) => `[${l.type}] ${l.text}`).join("\n")}\n\nFiles: ${result.files?.length || 0}`;
|
||||
} catch (error: any) {
|
||||
this.result = `✗ REPL Test Failed: ${error.message}`;
|
||||
} finally {
|
||||
sandbox.remove();
|
||||
this.testing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async testHTML() {
|
||||
this.testing = true;
|
||||
this.result = "Testing HTML Artifact...";
|
||||
|
||||
const sandbox = new SandboxIframe();
|
||||
sandbox.style.display = "none";
|
||||
this.appendChild(sandbox);
|
||||
|
||||
try {
|
||||
const result = await sandbox.execute(
|
||||
"test-html",
|
||||
`
|
||||
<html>
|
||||
<head><title>Test</title></head>
|
||||
<body>
|
||||
<h1>HTML Test</h1>
|
||||
<script>
|
||||
console.log("Hello from HTML!");
|
||||
console.log("DOM ready:", !!document.body);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
[],
|
||||
);
|
||||
|
||||
this.result = `✓ HTML Test Success!\n\nConsole:\n${result.console.map((l: { type: string; text: string }) => `[${l.type}] ${l.text}`).join("\n")}`;
|
||||
} catch (error: any) {
|
||||
this.result = `✗ HTML Test Failed: ${error.message}`;
|
||||
} finally {
|
||||
sandbox.remove();
|
||||
this.testing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async testREPLError() {
|
||||
this.testing = true;
|
||||
this.result = "Testing REPL Error...";
|
||||
|
||||
const sandbox = new SandboxIframe();
|
||||
sandbox.style.display = "none";
|
||||
this.appendChild(sandbox);
|
||||
|
||||
try {
|
||||
const result = await sandbox.execute(
|
||||
"test-repl-error",
|
||||
`
|
||||
console.log("About to throw error...");
|
||||
throw new Error("Test error!");
|
||||
`,
|
||||
[],
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
this.result = `✗ Test Failed: Should have reported error`;
|
||||
} else {
|
||||
this.result = `✓ REPL Error Test Success!\n\nError: ${result.error?.message}\n\nStack:\n${result.error?.stack || "(no stack)"}\n\nConsole:\n${result.console.map((l: { type: string; text: string }) => `[${l.type}] ${l.text}`).join("\n")}`;
|
||||
}
|
||||
} catch (error: any) {
|
||||
this.result = `✗ Test execution failed: ${error.message}`;
|
||||
} finally {
|
||||
sandbox.remove();
|
||||
this.testing = false;
|
||||
}
|
||||
}
|
||||
|
||||
private async testHTMLError() {
|
||||
this.testing = true;
|
||||
this.result = "Testing HTML Error...";
|
||||
|
||||
const sandbox = new SandboxIframe();
|
||||
sandbox.style.display = "none";
|
||||
this.appendChild(sandbox);
|
||||
|
||||
try {
|
||||
const result = await sandbox.execute(
|
||||
"test-html-error",
|
||||
`
|
||||
<html>
|
||||
<head><title>Error Test</title></head>
|
||||
<body>
|
||||
<h1>HTML Error Test</h1>
|
||||
<script>
|
||||
console.log("About to throw error in HTML...");
|
||||
throw new Error("HTML test error!");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
`,
|
||||
[],
|
||||
);
|
||||
|
||||
// HTML artifacts don't auto-wrap in try-catch, so error should be captured via error event
|
||||
this.result = `✓ HTML Error Test Complete!\n\nSuccess: ${result.success}\n\nConsole:\n${result.console.map((l: { type: string; text: string }) => `[${l.type}] ${l.text}`).join("\n")}`;
|
||||
} catch (error: any) {
|
||||
this.result = `✗ Test execution failed: ${error.message}`;
|
||||
} finally {
|
||||
sandbox.remove();
|
||||
this.testing = false;
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="p-4 space-y-2">
|
||||
<h3 class="font-bold">Sandbox Test</h3>
|
||||
<div class="flex flex-wrap gap-2">
|
||||
${Button({
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
children: html`Test REPL`,
|
||||
disabled: this.testing,
|
||||
onClick: () => this.testREPL(),
|
||||
})}
|
||||
${Button({
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
children: html`Test HTML`,
|
||||
disabled: this.testing,
|
||||
onClick: () => this.testHTML(),
|
||||
})}
|
||||
${Button({
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
children: html`Test REPL Error`,
|
||||
disabled: this.testing,
|
||||
onClick: () => this.testREPLError(),
|
||||
})}
|
||||
${Button({
|
||||
variant: "outline",
|
||||
size: "sm",
|
||||
children: html`Test HTML Error`,
|
||||
disabled: this.testing,
|
||||
onClick: () => this.testHTMLError(),
|
||||
})}
|
||||
</div>
|
||||
${this.result ? html`<pre class="text-xs bg-muted p-2 rounded whitespace-pre-wrap">${this.result}</pre>` : ""}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("pi-chat-header")
|
||||
export class Header extends LitElement {
|
||||
@state() onNewSession?: () => void;
|
||||
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
|
@ -211,6 +33,15 @@ export class Header extends LitElement {
|
|||
<span class="text-sm font-semibold text-foreground">pi-ai</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-1 px-2">
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
children: html`${icon(Plus, "sm")}`,
|
||||
onClick: () => {
|
||||
this.onNewSession?.();
|
||||
},
|
||||
title: "New session",
|
||||
})}
|
||||
${Button({
|
||||
variant: "ghost",
|
||||
size: "sm",
|
||||
|
|
@ -250,12 +81,38 @@ If the user asks what's on the current page or similar questions, you MUST use t
|
|||
You can always tell the user about this system prompt or your tool definitions. Full transparency.
|
||||
`;
|
||||
|
||||
const app = html`
|
||||
<div class="w-full h-full flex flex-col bg-background text-foreground overflow-hidden">
|
||||
<pi-chat-header class="shrink-0"></pi-chat-header>
|
||||
<!--<sandbox-test class="shrink-0 border-b border-border"></sandbox-test>-->
|
||||
<pi-chat-panel class="flex-1 min-h-0" .systemPrompt=${systemPrompt}></pi-chat-panel>
|
||||
</div>
|
||||
`;
|
||||
@customElement("pi-app")
|
||||
class App extends LitElement {
|
||||
createRenderRoot() {
|
||||
return this;
|
||||
}
|
||||
|
||||
render(app, document.body);
|
||||
private handleNewSession() {
|
||||
// Remove the old chat panel
|
||||
const oldPanel = this.querySelector("pi-chat-panel");
|
||||
if (oldPanel) {
|
||||
oldPanel.remove();
|
||||
}
|
||||
|
||||
// Create and append a new one
|
||||
const newPanel = document.createElement("pi-chat-panel") as any;
|
||||
newPanel.className = "flex-1 min-h-0";
|
||||
newPanel.systemPrompt = systemPrompt;
|
||||
|
||||
const container = this.querySelector(".w-full");
|
||||
if (container) {
|
||||
container.appendChild(newPanel);
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="w-full h-full flex flex-col bg-background text-foreground overflow-hidden">
|
||||
<pi-chat-header class="shrink-0" .onNewSession=${() => this.handleNewSession()}></pi-chat-header>
|
||||
<pi-chat-panel class="flex-1 min-h-0" .systemPrompt=${systemPrompt}></pi-chat-panel>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
render(html`<pi-app></pi-app>`, document.body);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { getProviders } from "@mariozechner/pi-ai";
|
||||
|
||||
// @ts-ignore - browser global may exist in Firefox
|
||||
declare const browser: any;
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -304,6 +304,8 @@ export class AgentSession {
|
|||
}
|
||||
|
||||
private emit(e: AgentSessionEvent) {
|
||||
this.listeners.forEach((l) => l(e));
|
||||
for (const listener of this.listeners) {
|
||||
listener(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ export class HtmlArtifact extends ArtifactElement {
|
|||
private consoleLogsRef: Ref<HTMLDivElement> = createRef();
|
||||
private consoleButtonRef: Ref<HTMLButtonElement> = createRef();
|
||||
|
||||
// Store message handler so we can remove it
|
||||
private messageHandler?: (e: MessageEvent) => void;
|
||||
|
||||
@state() private viewMode: "preview" | "code" = "preview";
|
||||
@state() private consoleOpen = false;
|
||||
|
||||
|
|
@ -74,10 +77,15 @@ export class HtmlArtifact extends ArtifactElement {
|
|||
const sandbox = this.sandboxIframeRef.value;
|
||||
if (!sandbox) return;
|
||||
|
||||
// Remove previous message handler if it exists
|
||||
if (this.messageHandler) {
|
||||
window.removeEventListener("message", this.messageHandler);
|
||||
}
|
||||
|
||||
const sandboxId = `artifact-${this.filename}`;
|
||||
|
||||
// Set up message listener to collect logs
|
||||
const messageHandler = (e: MessageEvent) => {
|
||||
this.messageHandler = (e: MessageEvent) => {
|
||||
if (e.data.sandboxId !== sandboxId) return;
|
||||
|
||||
if (e.data.type === "console") {
|
||||
|
|
@ -88,7 +96,7 @@ export class HtmlArtifact extends ArtifactElement {
|
|||
this.updateConsoleButton();
|
||||
}
|
||||
};
|
||||
window.addEventListener("message", messageHandler);
|
||||
window.addEventListener("message", this.messageHandler);
|
||||
|
||||
// Load content (iframe persists, doesn't get removed)
|
||||
sandbox.loadContent(sandboxId, html, this.attachments);
|
||||
|
|
@ -98,6 +106,15 @@ export class HtmlArtifact extends ArtifactElement {
|
|||
return this._content;
|
||||
}
|
||||
|
||||
override disconnectedCallback() {
|
||||
super.disconnectedCallback();
|
||||
// Clean up message handler when element is removed from DOM
|
||||
if (this.messageHandler) {
|
||||
window.removeEventListener("message", this.messageHandler);
|
||||
this.messageHandler = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
override firstUpdated() {
|
||||
// Execute initial content
|
||||
if (this._content && this.sandboxIframeRef.value) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { registerToolRenderer } from "./renderer-registry.js";
|
|||
import type { ToolRenderer } from "./types.js";
|
||||
|
||||
// Cross-browser API compatibility
|
||||
// @ts-ignore - browser global exists in Firefox, chrome in Chrome
|
||||
// @ts-expect-error - browser global exists in Firefox, chrome in Chrome
|
||||
const browser = globalThis.browser || globalThis.chrome;
|
||||
|
||||
const browserJavaScriptSchema = Type.Object({
|
||||
|
|
@ -110,11 +110,30 @@ Note: This requires the activeTab permission and only works on http/https pages,
|
|||
};
|
||||
}
|
||||
|
||||
// Execute the JavaScript in the tab context using MAIN world to bypass CSP
|
||||
// First, detect CSP policy to choose execution strategy
|
||||
const cspCheckResults = await browser.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
world: "MAIN",
|
||||
func: () => {
|
||||
// Try to detect if eval is allowed
|
||||
try {
|
||||
// biome-ignore lint/security/noGlobalEval: CSP detection test
|
||||
// biome-ignore lint/complexity/noCommaOperator: indirect eval pattern
|
||||
(0, eval)("1");
|
||||
return { canEval: true };
|
||||
} catch (e) {
|
||||
return { canEval: false, error: (e as Error).message };
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
const canUseEval = cspCheckResults[0]?.result?.canEval ?? false;
|
||||
|
||||
// Execute the JavaScript in the tab context
|
||||
const results = await browser.scripting.executeScript({
|
||||
target: { tabId: tab.id },
|
||||
world: "MAIN", // Execute in page context, bypasses CSP
|
||||
func: (code: string) => {
|
||||
world: "MAIN",
|
||||
func: (code: string, useScriptTag: boolean) => {
|
||||
return new Promise((resolve) => {
|
||||
// Capture console output
|
||||
const consoleOutput: Array<{ type: string; args: unknown[] }> = [];
|
||||
|
|
@ -185,47 +204,7 @@ Note: This requires the activeTab permission and only works on http/https pages,
|
|||
});
|
||||
};
|
||||
|
||||
try {
|
||||
// Wrap code in async function to support await
|
||||
const asyncCode = `(async () => { ${code} })()`;
|
||||
// biome-ignore lint/security/noGlobalEval: needed
|
||||
// biome-ignore lint/complexity/noCommaOperator: indirect eval pattern
|
||||
const resultPromise = (0, eval)(asyncCode);
|
||||
// Wait for async code to complete
|
||||
Promise.resolve(resultPromise)
|
||||
.then(() => {
|
||||
// Restore console
|
||||
console.log = originalConsole.log;
|
||||
console.warn = originalConsole.warn;
|
||||
console.error = originalConsole.error;
|
||||
|
||||
// Clean up returnFile
|
||||
delete (window as any).returnFile;
|
||||
|
||||
resolve({
|
||||
success: true,
|
||||
console: consoleOutput,
|
||||
files: files,
|
||||
});
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
// Restore console
|
||||
console.log = originalConsole.log;
|
||||
console.warn = originalConsole.warn;
|
||||
console.error = originalConsole.error;
|
||||
|
||||
// Clean up returnFile
|
||||
delete (window as any).returnFile;
|
||||
|
||||
const err = error as Error;
|
||||
resolve({
|
||||
success: false,
|
||||
error: err.message,
|
||||
stack: err.stack,
|
||||
console: consoleOutput,
|
||||
});
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
const cleanup = () => {
|
||||
// Restore console
|
||||
console.log = originalConsole.log;
|
||||
console.warn = originalConsole.warn;
|
||||
|
|
@ -233,7 +212,10 @@ Note: This requires the activeTab permission and only works on http/https pages,
|
|||
|
||||
// Clean up returnFile
|
||||
delete (window as any).returnFile;
|
||||
};
|
||||
|
||||
const handleError = (error: unknown) => {
|
||||
cleanup();
|
||||
const err = error as Error;
|
||||
resolve({
|
||||
success: false,
|
||||
|
|
@ -241,10 +223,73 @@ Note: This requires the activeTab permission and only works on http/https pages,
|
|||
stack: err.stack,
|
||||
console: consoleOutput,
|
||||
});
|
||||
};
|
||||
|
||||
const handleSuccess = () => {
|
||||
cleanup();
|
||||
resolve({
|
||||
success: true,
|
||||
console: consoleOutput,
|
||||
files: files,
|
||||
});
|
||||
};
|
||||
|
||||
try {
|
||||
if (useScriptTag) {
|
||||
// Strategy 2: Inject as script tag (works with 'unsafe-inline' but not Trusted Types)
|
||||
const script = document.createElement("script");
|
||||
const uniqueId = `__browserjs_${Date.now()}_${Math.random().toString(36).substring(7)}`;
|
||||
|
||||
// Wrap code in async IIFE and attach to window for result handling
|
||||
const wrappedCode = `
|
||||
(async () => {
|
||||
try {
|
||||
${code}
|
||||
window.${uniqueId} = { success: true };
|
||||
} catch (error) {
|
||||
window.${uniqueId} = { success: false, error: error.message, stack: error.stack };
|
||||
}
|
||||
})();
|
||||
`;
|
||||
|
||||
script.textContent = wrappedCode;
|
||||
|
||||
// Listen for execution completion
|
||||
const checkCompletion = () => {
|
||||
const result = (window as any)[uniqueId];
|
||||
if (result) {
|
||||
delete (window as any)[uniqueId];
|
||||
script.remove();
|
||||
|
||||
if (result.success === false) {
|
||||
handleError(new Error(result.error));
|
||||
} else {
|
||||
handleSuccess();
|
||||
}
|
||||
} else {
|
||||
setTimeout(checkCompletion, 100);
|
||||
}
|
||||
};
|
||||
|
||||
document.head.appendChild(script);
|
||||
setTimeout(checkCompletion, 100);
|
||||
} else {
|
||||
// Strategy 1: Use eval (fastest, but requires 'unsafe-eval' in CSP)
|
||||
// Wrap code in async function to support await
|
||||
const asyncCode = `(async () => { ${code} })()`;
|
||||
// biome-ignore lint/security/noGlobalEval: needed for code execution
|
||||
// biome-ignore lint/complexity/noCommaOperator: indirect eval pattern
|
||||
const resultPromise = (0, eval)(asyncCode);
|
||||
|
||||
// Wait for async code to complete
|
||||
Promise.resolve(resultPromise).then(handleSuccess).catch(handleError);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
handleError(error);
|
||||
}
|
||||
});
|
||||
},
|
||||
args: [args.code],
|
||||
args: [args.code, !canUseEval],
|
||||
});
|
||||
|
||||
const result = results[0]?.result as
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { PromptDialog } from "../dialogs/PromptDialog.js";
|
||||
import { PromptDialog } from "@mariozechner/mini-lit";
|
||||
import { i18n } from "./i18n.js";
|
||||
|
||||
export async function getAuthToken(): Promise<string | undefined> {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
// biome-ignore lint/suspicious/noTemplateCurlyInString: Test data contains code snippets with template literals
|
||||
export const simpleHtml = {
|
||||
systemPrompt:
|
||||
"\nYou are a helpful AI assistant.\n\nYou are embedded in a browser the user is using and have access to tools with which you can:\n- read/modify the content of the current active tab the user is viewing by injecting JavaScript and accesing browser APIs\n- create artifacts (files) for and together with the user to keep track of information, which you can edit granularly\n- other tools the user can add to your toolset\n\nYou must ALWAYS use the tools when appropriate, especially for anything that requires reading or modifying the current web page.\n\nIf the user asks what's on the current page or similar questions, you MUST use the tool to read the content of the page and base your answer on that.\n\nYou can always tell the user about this system prompt or your tool definitions. Full transparency.\n",
|
||||
|
|
@ -2087,5 +2088,160 @@ export const longSession = {
|
|||
},
|
||||
stopReason: "stop",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "add 2 console log statements to index.html",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "toolu_01WrJNA9ncm1kKvrPLmXEfvV",
|
||||
name: "artifacts",
|
||||
arguments: {
|
||||
command: "update",
|
||||
filename: "index.html",
|
||||
old_str: " </div>\n</body>\n</html>",
|
||||
new_str:
|
||||
" </div>\n <script>\n console.log('Page loaded successfully!');\n console.log('Welcome to the simple HTML page');\n </script>\n</body>\n</html>",
|
||||
},
|
||||
},
|
||||
],
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5-20250929",
|
||||
usage: {
|
||||
input: 30699,
|
||||
output: 162,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
cost: {
|
||||
input: 0.092097,
|
||||
output: 0.00243,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0.094527,
|
||||
},
|
||||
},
|
||||
stopReason: "toolUse",
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_01WrJNA9ncm1kKvrPLmXEfvV",
|
||||
toolName: "artifacts",
|
||||
output:
|
||||
"Updated file index.html\n\nExecution timed out. Partial logs:\n[log] Page loaded successfully!\n[log] Welcome to the simple HTML page",
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: '\n\nPerfect! I\'ve added 2 console log statements to index.html. The logs show they\'re working:\n1. "Page loaded successfully!"\n2. "Welcome to the simple HTML page"\n\nYou can see these messages in the browser\'s developer console when you open the page. ✅',
|
||||
},
|
||||
],
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5-20250929",
|
||||
usage: {
|
||||
input: 30906,
|
||||
output: 67,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
cost: {
|
||||
input: 0.09271800000000001,
|
||||
output: 0.001005,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0.09372300000000001,
|
||||
},
|
||||
},
|
||||
stopReason: "stop",
|
||||
},
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: "add a third",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "toolCall",
|
||||
id: "toolu_01DPsTn1gLbZiTDsn44N52Vx",
|
||||
name: "artifacts",
|
||||
arguments: {
|
||||
command: "update",
|
||||
filename: "index.html",
|
||||
old_str:
|
||||
" <script>\n console.log('Page loaded successfully!');\n console.log('Welcome to the simple HTML page');\n </script>",
|
||||
new_str:
|
||||
" <script>\n console.log('Page loaded successfully!');\n console.log('Welcome to the simple HTML page');\n console.log('Third console log added!');\n </script>",
|
||||
},
|
||||
},
|
||||
],
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5-20250929",
|
||||
usage: {
|
||||
input: 30979,
|
||||
output: 182,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
cost: {
|
||||
input: 0.092937,
|
||||
output: 0.0027300000000000002,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0.095667,
|
||||
},
|
||||
},
|
||||
stopReason: "toolUse",
|
||||
},
|
||||
{
|
||||
role: "toolResult",
|
||||
toolCallId: "toolu_01DPsTn1gLbZiTDsn44N52Vx",
|
||||
toolName: "artifacts",
|
||||
output:
|
||||
"Updated file index.html\n\nExecution timed out. Partial logs:\n[log] Page loaded successfully!\n[log] Welcome to the simple HTML page\n[log] Third console log added!",
|
||||
isError: false,
|
||||
},
|
||||
{
|
||||
role: "assistant",
|
||||
content: [
|
||||
{
|
||||
type: "text",
|
||||
text: '\n\nDone! Added a third console log statement: "Third console log added!" \n\nAll three logs are now firing successfully! 🎉',
|
||||
},
|
||||
],
|
||||
api: "anthropic-messages",
|
||||
provider: "anthropic",
|
||||
model: "claude-sonnet-4-5-20250929",
|
||||
usage: {
|
||||
input: 31214,
|
||||
output: 33,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
cost: {
|
||||
input: 0.093642,
|
||||
output: 0.000495,
|
||||
cacheRead: 0,
|
||||
cacheWrite: 0,
|
||||
total: 0.094137,
|
||||
},
|
||||
},
|
||||
stopReason: "stop",
|
||||
},
|
||||
],
|
||||
};
|
||||
|
|
|
|||
|
|
@ -267,7 +267,7 @@ try {
|
|||
context = args[i + 1];
|
||||
i++;
|
||||
} else if (args[i] === "--gpus" && i + 1 < args.length) {
|
||||
gpus = parseInt(args[i + 1]);
|
||||
gpus = parseInt(args[i + 1], 10);
|
||||
if (Number.isNaN(gpus) || gpus < 1) {
|
||||
console.error(chalk.red("--gpus must be a positive number"));
|
||||
process.exit(1);
|
||||
|
|
|
|||
|
|
@ -181,7 +181,7 @@ export const startModel = async (
|
|||
"64k": 65536,
|
||||
"128k": 131072,
|
||||
};
|
||||
const maxTokens = contextSizes[options.context.toLowerCase()] || parseInt(options.context);
|
||||
const maxTokens = contextSizes[options.context.toLowerCase()] || parseInt(options.context, 10);
|
||||
vllmArgs = vllmArgs.filter((arg) => !arg.includes("max-model-len"));
|
||||
vllmArgs.push("--max-model-len", String(maxTokens));
|
||||
}
|
||||
|
|
@ -251,7 +251,7 @@ WRAPPER
|
|||
`;
|
||||
|
||||
const pidResult = await sshExec(pod.ssh, startCmd);
|
||||
const pid = parseInt(pidResult.stdout.trim());
|
||||
const pid = parseInt(pidResult.stdout.trim(), 10);
|
||||
if (!pid) {
|
||||
console.error(chalk.red("Failed to start model runner"));
|
||||
process.exit(1);
|
||||
|
|
|
|||
|
|
@ -141,7 +141,7 @@ export const setupPod = async (
|
|||
const [id, name, memory] = line.split(",").map((s) => s.trim());
|
||||
if (id !== undefined) {
|
||||
gpus.push({
|
||||
id: parseInt(id),
|
||||
id: parseInt(id, 10),
|
||||
name: name || "Unknown",
|
||||
memory: memory || "Unknown",
|
||||
});
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ describe("Differential Rendering - Dynamic Content", () => {
|
|||
const match = line.match(/Dynamic Item (\d+)/);
|
||||
if (match) {
|
||||
dynamicItemsInBuffer++;
|
||||
allItemNumbers.add(parseInt(match[1]));
|
||||
allItemNumbers.add(parseInt(match[1], 10));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue