yao/agent/sandbox/v2/claude/testdata/code.ts
Max 3f390c223c feat(sandbox/v2): enhance V2 sandbox integration and testing
- Implemented V2 sandbox initialization in the assistant loading process, allowing for standalone sandbox.yao configuration.
- Added support for V2 sandbox execution paths in the Assistant's Stream method, differentiating between V1 and V2 sandboxes.
- Introduced comprehensive tests for V2 sandbox configurations, ensuring correct loading and execution behavior.
- Updated the context and types to accommodate V2 sandbox features, including system information and workspace management.

Made-with: Cursor
2026-03-10 01:13:17 +08:00

904 lines
27 KiB
TypeScript

import { Process } from "@yao/runtime";
/**
* Excel class for manipulating Excel files via Yao's Excel Module
*/
export class Excel {
private handle: string | null = null;
/**
* Creates a new Excel instance
* @param file Path to the Excel file
*/
constructor(private file: string, writable: boolean = false) {
this.file = file;
this.Open(writable);
}
/**
* Read each sheet top n rows
* @param file Path to the Excel file
* @param n number of rows to read
* @returns Object with sheet names as keys and arrays of row values as values
*/
static Heads(
file: string,
n: number = 5,
filters?: string[]
): Record<string, any[][]> {
const excel = new Excel(file);
const heads = excel.Heads(n, filters);
excel.Close();
return heads;
}
/**
* Read each sheet top n rows
* @param n number of rows to read
* @returns Object with sheet names as keys and arrays of row values as values
* @throws Error if file not opened
*/
Heads(n: number = 5, filters: string[] = []): Record<string, any[][]> {
if (!this.handle) throw new Error("Excel file not opened");
const sheets = this.Sheets();
const result: Record<string, any[][]> = {};
for (const sheet of sheets) {
if (filters.length > 0 && !filters.includes(sheet)) {
continue;
}
// Open row iterator for the sheet
const iterator = this.each.OpenRow(sheet);
const rows: any[][] = [];
// Read n rows
let row;
let count = 0;
while (
count < n &&
(row = Process(`excel.each.NextRow`, iterator)) !== null
) {
// Add column headers (A, B, C, ...) for the first row
if (count === 0) {
const headerRow = [];
for (let i = 0; i < row.length; i++) {
headerRow.push(this.convert.ColumnNumberToName(i + 1));
}
rows.push(headerRow);
}
// Trim Each cell's value
row = row.map((cell) => cell?.trim?.());
rows.push(row);
count++;
}
// Close the row iterator
this.each.CloseRow(iterator);
// Find the max length of each row, and pad the column headers(A, B, C, ...) to the same length
const maxLength = Math.max(...rows.map((row) => row.length));
const start = rows[0].length;
const neededLength = maxLength - rows[0].length;
for (let i = 0; i < neededLength; i++) {
rows[0].push(this.convert.ColumnNumberToName(start + i + 1));
}
// Add the sheet's rows to the result
result[sheet] = rows;
}
return result;
}
/**
* Check if a sheet exists in the Excel file
* @param file Path to the Excel file
* @param sheet Sheet name to check
* @returns boolean - true if sheet exists, false otherwise
*/
static Exists(file: string, sheet: string) {
const excel = new Excel(file);
const exists = excel.sheet.Exists(sheet);
excel.Close();
return exists;
}
/**
* Opens an Excel file for reading or writing
* @param writable Whether to open in writable mode (true) or read-only mode (false)
* @returns Handle ID used for subsequent operations
*/
Open(writable: boolean = false) {
this.handle = Process(`excel.Open`, this.file, writable);
return this.handle;
}
/**
* Closes the Excel file and releases resources
* IMPORTANT: Always call this method when done to prevent memory leaks
*/
Close() {
if (this.handle) {
Process(`excel.Close`, this.handle);
this.handle = null;
}
}
/**
* Saves changes to the Excel file
* @throws Error if file not opened
*/
Save() {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.Save`, this.handle);
}
/**
* Gets all sheet names in the workbook
* @returns Array of sheet names
* @throws Error if file not opened
*/
Sheets() {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.Sheets`, this.handle);
}
// Sheet operations
sheet = {
/**
* Creates a new sheet in the workbook
* @param name Name for the new sheet
* @returns number Index of the new sheet
* @throws Error if file not opened
*/
Create: (name: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.create`, this.handle, name);
},
/**
* Lists all sheets in the workbook
* @returns string[] Array of sheet names
* @throws Error if file not opened
*/
List: () => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.list`, this.handle);
},
/**
* Checks if a sheet exists in the workbook
* @param name Sheet name to check
* @returns boolean - true if sheet exists, false otherwise
* @throws Error if file not opened
*/
Exists: (name: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.exists`, this.handle, name);
},
/**
* Reads all data from a sheet
* @param name Sheet name
* @returns any[][] Two-dimensional array of cell values
* @throws Error if file not opened
*/
Read: (name: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.read`, this.handle, name);
},
/**
* Reads all data from a sheet with pagination support
* @param name Sheet name
* @param from Starting row index (0-based)
* @param chunk_size Number of rows to read
* @returns any[][] Two-dimensional array of cell values
* @throws Error if file not opened
*/
Rows: (name: string, from: number, chunk_size: number) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.rows`, this.handle, name, from, chunk_size);
},
/**
* Updates data in a sheet. Creates the sheet if it doesn't exist.
* @param name Sheet name
* @param data Two-dimensional array of values to write
* @throws Error if file not opened
*/
Update: (name: string, data: any[][]) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.update`, this.handle, name, data);
},
/**
* Copies a sheet with all its content and formatting
* @param source Source sheet name
* @param target Target sheet name (must not exist)
* @throws Error if file not opened
*/
Copy: (source: string, target: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.copy`, this.handle, source, target);
},
/**
* Deletes a sheet from the workbook
* @param name Sheet name to delete
* @throws Error if file not opened
*/
Delete: (name: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.delete`, this.handle, name);
},
/**
* Gets the dimensions (number of rows and columns) of a sheet
* @param name Sheet name
* @returns {rows: number, cols: number} - Object containing row and column counts
* @throws Error if file not opened
*/
Dimension: (name: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.sheet.dimension`, this.handle, name);
},
};
// Reading operations
read = {
/**
* Reads a cell's value
* @param sheet Sheet name
* @param cell Cell reference (e.g. "A1")
* @returns Cell value
* @throws Error if file not opened
*/
Cell: (sheet: string, cell: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.read.Cell`, this.handle, sheet, cell);
},
/**
* Reads all rows in a sheet
* @param sheet Sheet name
* @returns Two-dimensional array of cell values
* @throws Error if file not opened
*/
Row: (sheet: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.read.Row`, this.handle, sheet);
},
/**
* Reads all columns in a sheet
* @param sheet Sheet name
* @returns Two-dimensional array of cell values
* @throws Error if file not opened
*/
Column: (sheet: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.read.Column`, this.handle, sheet);
},
};
// Writing operations
write = {
/**
* Writes a value to a cell
* @param sheet Sheet name
* @param cell Cell reference (e.g. "A1")
* @param value Value to write (string, number, boolean, etc.)
* @throws Error if file not opened
*/
Cell: (sheet: string, cell: string, value: any) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.write.Cell`, this.handle, sheet, cell, value);
},
/**
* Writes values to a row starting at the specified cell
* @param sheet Sheet name
* @param startCell Starting cell reference (e.g. "A1")
* @param values Array of values to write
* @throws Error if file not opened
*/
Row: (sheet: string, startCell: string, values: any[]) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.write.Row`, this.handle, sheet, startCell, values);
},
/**
* Writes values to a column starting at the specified cell
* @param sheet Sheet name
* @param startCell Starting cell reference (e.g. "A1")
* @param values Array of values to write
* @throws Error if file not opened
*/
Column: (sheet: string, startCell: string, values: any[]) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(
`excel.write.Column`,
this.handle,
sheet,
startCell,
values
);
},
/**
* Writes a two-dimensional array of values starting at the specified cell
* @param sheet Sheet name
* @param startCell Starting cell reference (e.g. "A1")
* @param values Two-dimensional array of values to write
* @throws Error if file not opened
*/
All: (sheet: string, startCell: string, values: any[][]) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.write.All`, this.handle, sheet, startCell, values);
},
};
// Setting properties
set = {
/**
* Applies a style to a cell
* @param sheet Sheet name
* @param cell Cell reference (e.g. "A1")
* @param styleID Style ID to apply
* @throws Error if file not opened
*/
Style: (sheet: string, cell: string, styleID: number) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.set.Style`, this.handle, sheet, cell, styleID);
},
/**
* Sets a row's height
* @param sheet Sheet name
* @param row Row number
* @param height Height in points
* @throws Error if file not opened
*/
RowHeight: (sheet: string, row: number, height: number) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.set.RowHeight`, this.handle, sheet, row, height);
},
/**
* Sets column width for a range of columns
* @param sheet Sheet name
* @param startCol Starting column letter
* @param endCol Ending column letter
* @param width Width in points
* @throws Error if file not opened
*/
ColumnWidth: (
sheet: string,
startCol: string,
endCol: string,
width: number
) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(
`excel.set.ColumnWidth`,
this.handle,
sheet,
startCol,
endCol,
width
);
},
/**
* Merges cells in a range
* @param sheet Sheet name
* @param startCell Starting cell reference (e.g. "A1")
* @param endCell Ending cell reference (e.g. "B2")
* @throws Error if file not opened
*/
MergeCell: (sheet: string, startCell: string, endCell: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(
`excel.set.MergeCell`,
this.handle,
sheet,
startCell,
endCell
);
},
/**
* Unmerges previously merged cells
* @param sheet Sheet name
* @param startCell Starting cell reference (e.g. "A1")
* @param endCell Ending cell reference (e.g. "B2")
* @throws Error if file not opened
*/
UnmergeCell: (sheet: string, startCell: string, endCell: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(
`excel.set.UnmergeCell`,
this.handle,
sheet,
startCell,
endCell
);
},
/**
* Sets a formula in a cell
* @param sheet Sheet name
* @param cell Cell reference (e.g. "C1")
* @param formula Excel formula without the leading equals sign
* @throws Error if file not opened
*/
Formula: (sheet: string, cell: string, formula: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.set.Formula`, this.handle, sheet, cell, formula);
},
/**
* Adds a hyperlink to a cell
* @param sheet Sheet name
* @param cell Cell reference (e.g. "A1")
* @param url URL for the hyperlink
* @param text Display text for the hyperlink
* @throws Error if file not opened
*/
Link: (sheet: string, cell: string, url: string, text: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.set.Link`, this.handle, sheet, cell, url, text);
},
};
// Iteration methods
each = {
/**
* Opens a row iterator
* @param sheet Sheet name
* @returns Row iterator ID
* @throws Error if file not opened
*/
OpenRow: (sheet: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.each.OpenRow`, this.handle, sheet);
},
/**
* Gets the next row from the iterator
* @param rowID Row iterator ID from excel.each.OpenRow
* @returns Array of cell values or null if no more rows
*/
NextRow: (rowID: string) => {
return Process(`excel.each.NextRow`, rowID);
},
/**
* Closes the row iterator
* @param rowID Row iterator ID from excel.each.OpenRow
*/
CloseRow: (rowID: string) => {
return Process(`excel.each.CloseRow`, rowID);
},
/**
* Opens a column iterator
* @param sheet Sheet name
* @returns Column iterator ID
* @throws Error if file not opened
*/
OpenColumn: (sheet: string) => {
if (!this.handle) throw new Error("Excel file not opened");
return Process(`excel.each.OpenColumn`, this.handle, sheet);
},
/**
* Gets the next column from the iterator
* @param colID Column iterator ID from excel.each.OpenColumn
* @returns Array of cell values or null if no more columns
*/
NextColumn: (colID: string) => {
return Process(`excel.each.NextColumn`, colID);
},
/**
* Closes the column iterator
* @param colID Column iterator ID from excel.each.OpenColumn
*/
CloseColumn: (colID: string) => {
return Process(`excel.each.CloseColumn`, colID);
},
};
// Conversion utilities
convert = {
/**
* Converts a column name to a column number
* @param colName Column name (e.g. "A", "AB")
* @returns Column number (1-based)
*/
ColumnNameToNumber: (colName: string) => {
return Process(`excel.convert.ColumnNameToNumber`, colName);
},
/**
* Converts a column number to a column name
* @param colNum Column number (1-based)
* @returns Column name
*/
ColumnNumberToName: (colNum: number) => {
return Process(`excel.convert.ColumnNumberToName`, colNum);
},
/**
* Converts a cell reference to coordinates
* @param cell Cell reference (e.g. "A1")
* @returns Array with [columnNumber, rowNumber] (1-based)
*/
CellNameToCoordinates: (cell: string) => {
return Process(`excel.convert.CellNameToCoordinates`, cell);
},
/**
* Converts coordinates to a cell reference
* @param col Column number (1-based)
* @param row Row number (1-based)
* @returns Cell reference
*/
CoordinatesToCellName: (col: number, row: number) => {
return Process(`excel.convert.CoordinatesToCellName`, col, row);
},
};
}
/**
* Volcengine OpenAPI SDK
*/
import { Exception, http, Process } from "@yao/runtime";
export class Volcengine {
private AccessKeyId: string;
private SecretAccessKey: string;
private Region: string;
private Service: string;
private Endpoint: string;
constructor(option: Option) {
this.AccessKeyId = option.AccessKeyId;
this.SecretAccessKey = option.SecretAccessKey;
this.Region = option.Region;
this.Service = option.Service;
this.Endpoint = option.Endpoint
? `https://${option.Endpoint}`
: `https://${this.Service}.${this.Region}.volcengineapi.com`;
}
public Get(query: Record<string, string>) {
const url = this.Endpoint;
const host = url.split("://")[1].split("/")[0];
const headers = { host: host };
const request: Request = {
Method: "GET",
URI: "/",
Query: query,
Headers: headers,
Payload: null,
};
const auth = this.getAuthorization(request);
// Add authorization header
headers["Authorization"] = auth;
headers["Content-Type"] = "application/json";
const resp = http.Get(url, query, headers);
if (resp.code > 299 || resp.code < 200) {
const { ResponseMetadata } = resp.data || {};
const { Error } = ResponseMetadata || {};
const message =
Error?.Message || (resp.code === 0 ? resp.message : "Unknown error");
throw new Exception(message, resp.code);
}
return resp.data;
}
/**
* Post request
* @param query Query parameters
* @param payload Payload
* @returns Response
*/
public Post(query: Record<string, string>, payload: Record<string, any>) {
const url = this.Endpoint;
const host = url.split("://")[1].split("/")[0];
const headers = { host: host };
const body = JSON.stringify(payload);
const request: Request = {
Method: "POST",
URI: "/",
Query: query,
Headers: headers,
Payload: body,
};
const auth = this.getAuthorization(request);
headers["Authorization"] = auth;
headers["Content-Type"] = "application/json";
const resp = http.Post(url, body, null, query, headers);
if (resp.code > 299 || resp.code < 200) {
const { ResponseMetadata } = resp.data || {};
const { Error } = ResponseMetadata || {};
const message =
Error?.Message || (resp.code === 0 ? resp.message : "Unknown error");
throw new Exception(message, resp.code);
}
return resp.data;
}
/**
* Create a canonical request
* @param request Request object
* @returns Canonical request string
*/
private canonicalRequest(request: Request): string {
const xDate = this.formatDate(new Date());
// 1. HTTP Method
const method = request.Method;
// 2. URI (default to '/' if null)
const uri = request.URI || "/";
// 3. Query String
let queryString = "";
if (request.Query) {
if (Array.isArray(request.Query)) {
// Handle array of query parameters
const queryParams = request.Query.reduce((acc: string[], curr) => {
Object.entries(curr).forEach(([key, value]) => {
if (value !== null && value !== undefined && value !== "") {
acc.push(
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
);
}
});
return acc;
}, []);
queryString = queryParams.sort().join("&");
} else {
// Handle single query object
const queryParams = Object.entries(request.Query)
.filter(
([_, value]) =>
value !== null && value !== undefined && value !== ""
)
.map(
([key, value]) =>
`${encodeURIComponent(key)}=${encodeURIComponent(value)}`
)
.sort();
queryString = queryParams.join("&");
}
}
// 4. Headers
// First, collect all headers in a normalized format
const headers: Record<string, string> = { "x-date": xDate };
if (request.Headers) {
if (Array.isArray(request.Headers)) {
request.Headers.forEach((headerObj) => {
Object.entries(headerObj).forEach(([key, value]) => {
if (value !== null && value !== undefined && value.trim() !== "") {
headers[key.toLowerCase()] = value.trim();
}
});
});
} else {
Object.entries(request.Headers).forEach(([key, value]) => {
if (value !== null && value !== undefined && value.trim() !== "") {
headers[key.toLowerCase()] = value.trim();
}
});
}
}
// Get required headers if they exist
const signedHeaderKeys: string[] = [];
const requiredHeaders = ["host", "x-date"];
// Add required headers first if they exist
requiredHeaders.forEach((key) => {
if (headers[key]) {
signedHeaderKeys.push(key);
}
});
// Add any additional headers
// const additionalHeaders = Object.keys(headers)
// .filter((key) => !requiredHeaders.includes(key))
// .sort();
// signedHeaderKeys.push(...additionalHeaders);
// Build canonical headers string
const canonicalHeaders = signedHeaderKeys
.map((key) => `${key}:${headers[key]}`)
.join("\n");
// Build signed headers string
const signedHeaders = signedHeaderKeys.join(";");
// 5. Payload/Body
let hashedPayload = Process("crypto.Hash", "SHA256", "");
if (request.Payload !== null && request.Payload !== undefined) {
if (typeof request.Payload === "string") {
if (request.Payload !== "") {
hashedPayload = Process("crypto.Hash", "SHA256", request.Payload);
}
} else {
const payload = JSON.stringify(request.Payload);
if (payload !== "{}" && payload !== "[]") {
hashedPayload = Process("crypto.Hash", "SHA256", payload);
}
}
}
// Combine all components
const parts = [
method,
uri,
queryString,
canonicalHeaders,
"", // Empty line after headers
signedHeaders,
hashedPayload,
];
return parts.join("\n");
}
/**
* Format date to YYYYMMDDTHHMMSSZ
* @param date Date object
* @returns Formatted date string
*/
private formatDate(date: Date): string {
const year = date.getUTCFullYear();
const month = String(date.getUTCMonth() + 1).padStart(2, "0");
const day = String(date.getUTCDate()).padStart(2, "0");
const hours = String(date.getUTCHours()).padStart(2, "0");
const minutes = String(date.getUTCMinutes()).padStart(2, "0");
const seconds = String(date.getUTCSeconds()).padStart(2, "0");
return `${year}${month}${day}T${hours}${minutes}${seconds}Z`;
}
/**
* Create string to sign
* @param canonicalRequest Canonical request string
* @returns String to sign
*/
private stringToSign(canonicalRequest: string): string {
const algorithm = "HMAC-SHA256";
const requestDateTime = this.formatDate(new Date());
const requestDate = requestDateTime.slice(0, 8);
const credentialScope = `${requestDate}/${this.Region}/${this.Service}/request`; // YYYYMMDD
const hashedCanonicalRequest = Process(
"crypto.Hash",
"SHA256",
canonicalRequest
);
return `${algorithm}\n${requestDateTime}\n${credentialScope}\n${hashedCanonicalRequest}`;
}
/**
* Derive signing key
* @param date Date in YYYY/MM/DD format
* @returns Signing key
*/
private getSigningKey(date: string): string {
const kDate = Process("crypto.HMAC", "SHA256", date, this.SecretAccessKey);
const kRegion = Process(
"crypto.HMACWith",
{ key: "hex" },
this.Region,
kDate
);
const kService = Process(
"crypto.HMACWith",
{ key: "hex" },
this.Service,
kRegion
);
const kSigning = Process(
"crypto.HMACWith",
{ key: "hex" },
"request",
kService
);
return kSigning;
}
/**
* Calculate signature
* @param stringToSign String to sign
* @param signingKey Signing key
* @returns Signature
*/
private signature(stringToSign: string, signingKey: string): string {
return Process("crypto.HMACWith", { key: "hex" }, stringToSign, signingKey);
}
/**
* Build authorization header
* @param request Request object
* @returns Authorization header value
*/
public getAuthorization(request: Request): string {
const xDate = this.formatDate(new Date());
if (request.Headers) {
if (typeof request.Headers === "object") {
request.Headers["x-date"] = request.Headers["x-date"]
? request.Headers["x-date"]
: xDate;
}
}
// 1. Create canonical request
const canonicalReq = this.canonicalRequest(request);
// 2. Create string to sign
const stringToSign = this.stringToSign(canonicalReq);
// 3. Get date from string to sign
const [algorithm, requestDateTime, credentialScope] =
stringToSign.split("\n");
const date = requestDateTime.slice(0, 8);
// 4. Derive signing key
const signingKey = this.getSigningKey(date);
// 5. Calculate signature
const signature = this.signature(stringToSign, signingKey);
// 6. Build authorization header
let signedHeaders = "";
if (request.Headers) {
const headers: Record<string, string> = {};
if (Array.isArray(request.Headers)) {
request.Headers.forEach((headerObj) => {
Object.entries(headerObj).forEach(([key, value]) => {
headers[key.toLowerCase()] = value.trim();
});
});
} else {
Object.entries(request.Headers).forEach(([key, value]) => {
headers[key.toLowerCase()] = value.trim();
});
}
signedHeaders = Object.keys(headers).sort().join(";");
}
return `${algorithm} Credential=${this.AccessKeyId}/${credentialScope}, SignedHeaders=${signedHeaders}, Signature=${signature}`;
}
}
export interface Option {
AccessKeyId: string;
SecretAccessKey: string;
Endpoint?: string;
Region: string;
Service: string;
}
export interface Request {
Method: "GET" | "POST";
URI: string | null; // Default /
Query: Record<string, string> | Record<string, string>[] | null;
Headers: Record<string, string> | Record<string, string>[] | null;
Payload: string | Record<string, any> | any[] | null;
}