fix: reproducible build — pin deps, patch commander, move stubs to stubs/

- Move private package stubs to stubs/ (tracked), reference via file: in package.json
  Stubs: color-diff-napi, modifiers-napi, @ant/claude-for-chrome-mcp,
         @anthropic-ai/mcpb, @anthropic-ai/sandbox-runtime
- Add patches/commander@14.0.3.patch via pnpm patch:
  Allow multi-char short flags (/^-[^-]+$/) so -d2e works with commander v14
- Pin all dependency versions to exact versions from original cli.js bundle
  (extracted from node_modules package.json files in source map)
- Add @anthropic-ai/bedrock-sdk, foundry-sdk, vertex-sdk to dependencies
- Update .gitignore: node_modules/ fully ignored (regenerated by pnpm install)
- Update scripts to use plain `bun` instead of hardcoded path
- Verified: rm -rf node_modules && pnpm install && bun run build.ts succeeds
  Output: dist/cli.js (22.1MB), `bun dist/cli.js --version` → 2.1.88 (Claude Code)
This commit is contained in:
janlaywss
2026-03-31 21:24:59 +08:00
parent 8af48ca230
commit 84275122fe
2875 changed files with 866 additions and 462798 deletions

View File

@@ -1,50 +0,0 @@
import ansiStyles from "ansi-styles";
export const ESCAPES = new Set([27, 155]); // \x1b and \x9b
export const CSI = "[".codePointAt(0);
export const OSC = "]".codePointAt(0);
export const endCodesSet = new Set();
const endCodesMap = new Map();
for (const [start, end] of ansiStyles.codes) {
endCodesSet.add(ansiStyles.color.ansi(end));
endCodesMap.set(ansiStyles.color.ansi(start), ansiStyles.color.ansi(end));
}
export const linkStartCodePrefix = "\x1B]8;;";
export const linkStartCodePrefixCharCodes = linkStartCodePrefix
.split("")
.map((char) => char.charCodeAt(0));
export const linkCodeSuffix = "\x07";
export const linkCodeSuffixCharCode = linkCodeSuffix.charCodeAt(0);
export const linkEndCode = `\x1B]8;;${linkCodeSuffix}`;
export function getLinkStartCode(url) {
return `${linkStartCodePrefix}${url}${linkCodeSuffix}`;
}
export function getEndCode(code) {
if (endCodesSet.has(code))
return code;
if (endCodesMap.has(code))
return endCodesMap.get(code);
// We have a few special cases to handle here:
// Links:
if (code.startsWith(linkStartCodePrefix))
return linkEndCode;
code = code.slice(2);
// 8-bit/24-bit colors:
if (code.startsWith("38")) {
return ansiStyles.color.close;
}
else if (code.startsWith("48")) {
return ansiStyles.bgColor.close;
}
// Otherwise find the reset code in the ansi-styles map
const ret = ansiStyles.codes.get(parseInt(code, 10));
if (ret) {
return ansiStyles.color.ansi(ret);
}
else {
return ansiStyles.reset.open;
}
}
export function ansiCodesToString(codes) {
return codes.map((code) => code.code).join("");
}
//# sourceMappingURL=ansiCodes.js.map

View File

@@ -1,17 +0,0 @@
import { undoAnsiCodes } from "./undo.js";
/**
* Returns the minimum amount of ANSI codes necessary to get from the compound style `from` to `to`.
* Both `from` and `to` are expected to be reduced.
*/
export function diffAnsiCodes(from, to) {
const endCodesInTo = new Set(to.map((code) => code.endCode));
const startCodesInFrom = new Set(from.map((code) => code.code));
return [
// Ignore all styles in `from` that are not overwritten or removed by `to`
// Disable all styles in `from` that are removed in `to`
...undoAnsiCodes(from.filter((code) => !endCodesInTo.has(code.endCode))),
// Add all styles in `to` that don't exist in `from`
...to.filter((code) => !startCodesInFrom.has(code.code)),
];
}
//# sourceMappingURL=diff.js.map

View File

@@ -1,7 +0,0 @@
export { ansiCodesToString } from "./ansiCodes.js";
export { diffAnsiCodes } from "./diff.js";
export { reduceAnsiCodes, reduceAnsiCodesIncremental } from "./reduce.js";
export * from "./styledChars.js";
export * from "./tokenize.js";
export { undoAnsiCodes } from "./undo.js";
//# sourceMappingURL=index.js.map

View File

@@ -1,38 +0,0 @@
import ansiStyles from "ansi-styles";
import { endCodesSet } from "./ansiCodes.js";
/** Reduces the given array of ANSI codes to the minimum necessary to render with the same style */
export function reduceAnsiCodes(codes) {
return reduceAnsiCodesIncremental([], codes);
}
/** Like {@link reduceAnsiCodes}, but assumes that `codes` is already reduced. Further reductions are only done for the items in `newCodes`. */
export function reduceAnsiCodesIncremental(codes, newCodes) {
let ret = [...codes];
for (const code of newCodes) {
if (code.code === ansiStyles.reset.open) {
// Reset code, disable all codes
ret = [];
}
else if (endCodesSet.has(code.code)) {
// This is an end code, disable all matching start codes
ret = ret.filter((retCode) => retCode.endCode !== code.code);
}
else {
// This is a start code. Remove codes it "overrides", then add it.
// If a new code has the same endCode, it "overrides" existing ones.
// Special case: Intensity codes (1m, 2m) can coexist (both end with 22m).
const isIntensityCode = code.code === ansiStyles.bold.open || code.code === ansiStyles.dim.open;
// Add intensity codes only if not already present
if (isIntensityCode) {
if (!ret.find((retCode) => retCode.code === code.code && retCode.endCode === code.endCode)) {
ret.push(code);
}
}
else {
ret = ret.filter((retCode) => retCode.endCode !== code.endCode);
ret.push(code);
}
}
}
return ret;
}
//# sourceMappingURL=reduce.js.map

View File

@@ -1,38 +0,0 @@
import { ansiCodesToString } from "./ansiCodes.js";
import { diffAnsiCodes } from "./diff.js";
import { reduceAnsiCodesIncremental } from "./reduce.js";
export function styledCharsFromTokens(tokens) {
let codes = [];
const ret = [];
for (const token of tokens) {
if (token.type === "ansi") {
codes = reduceAnsiCodesIncremental(codes, [token]);
}
else if (token.type === "char") {
ret.push({
...token,
styles: [...codes],
});
}
}
return ret;
}
export function styledCharsToString(chars) {
let ret = "";
for (let i = 0; i < chars.length; i++) {
const char = chars[i];
if (i === 0) {
ret += ansiCodesToString(char.styles);
}
else {
ret += ansiCodesToString(diffAnsiCodes(chars[i - 1].styles, char.styles));
}
ret += char.value;
// reset active styles at the end of the string
if (i === chars.length - 1) {
ret += ansiCodesToString(diffAnsiCodes(char.styles, []));
}
}
return ret;
}
//# sourceMappingURL=styledChars.js.map

View File

@@ -1,141 +0,0 @@
import isFullwidthCodePoint from "is-fullwidth-code-point";
import { CSI, ESCAPES, getEndCode, linkStartCodePrefix, linkStartCodePrefixCharCodes, OSC, } from "./ansiCodes.js";
// HOT PATH: Use only basic string/char code operations for maximum performance
function parseLinkCode(string, offset) {
string = string.slice(offset);
for (let index = 1; index < linkStartCodePrefixCharCodes.length; index++) {
if (string.charCodeAt(index) !== linkStartCodePrefixCharCodes[index]) {
return undefined;
}
}
// This is a link code (with or without the URL part). Find the end of it.
const endIndex = string.indexOf("\x07", linkStartCodePrefix.length);
if (endIndex === -1)
return undefined;
return string.slice(0, endIndex + 1);
}
const CC_0 = "0".charCodeAt(0);
const CC_9 = "9".charCodeAt(0);
const CC_SEMI = ";".charCodeAt(0);
const CC_M = "m".charCodeAt(0);
/**
* Scans through the given string and finds the index of the last character of an SGR sequence
* like `\x1B[38;2;123;123;123m`. This assumes that the string has been checked to start with `\x1B[`.
* Returns -1 if no valid SGR sequence is found.
*/
function findSGRSequenceEndIndex(str) {
for (let index = 2; index < str.length; index++) {
const charCode = str.charCodeAt(index);
// m marks the end of the SGR sequence
if (charCode === CC_M)
return index;
// Digits and semicolons are valid
if (charCode === CC_SEMI)
continue;
if (charCode >= CC_0 && charCode <= CC_9)
continue;
// Everything else is invalid
break;
}
return -1;
}
// HOT PATH: Use only basic string/char code operations for maximum performance
function parseSGRSequence(string, offset) {
string = string.slice(offset);
const endIndex = findSGRSequenceEndIndex(string);
if (endIndex === -1)
return;
return string.slice(0, endIndex + 1);
}
/**
* Splits compound SGR sequences like `\x1B[1;3;31m` into individual components
*/
function splitCompoundSGRSequences(code) {
if (!code.includes(";")) {
// Not a compound code
return [code];
}
const codeParts = code
// Strip off the escape sequences \x1B[ and m
.slice(2, -1)
.split(";");
const ret = [];
for (let i = 0; i < codeParts.length; i++) {
const rawCode = codeParts[i];
// Keep 8-bit and 24-bit color codes (containing multiple ";") together
if (rawCode === "38" || rawCode === "48") {
if (i + 2 < codeParts.length && codeParts[i + 1] === "5") {
// 8-bit color, followed by another number
ret.push(codeParts.slice(i, i + 3).join(";"));
i += 2;
continue;
}
else if (i + 4 < codeParts.length && codeParts[i + 1] === "2") {
// 24-bit color, followed by three numbers
ret.push(codeParts.slice(i, i + 5).join(";"));
i += 4;
continue;
}
}
// Not a (valid) 8/24-bit color code, push as is
ret.push(rawCode);
}
return ret.map((part) => `\x1b[${part}m`);
}
export function tokenize(str, endChar = Number.POSITIVE_INFINITY) {
const ret = [];
let index = 0;
let visible = 0;
while (index < str.length) {
const codePoint = str.codePointAt(index);
if (ESCAPES.has(codePoint)) {
let code;
// Peek the next code point to determine the type of ANSI sequence
const nextCodePoint = str.codePointAt(index + 1);
if (nextCodePoint === OSC) {
// ] = operating system commands, like links
code = parseLinkCode(str, index);
if (code) {
ret.push({
type: "ansi",
code: code,
endCode: getEndCode(code),
});
}
}
else if (nextCodePoint === CSI) {
// [ = control sequence introducer, like SGR sequences [...m
code = parseSGRSequence(str, index);
if (code) {
// Split compound codes into individual tokens
const codes = splitCompoundSGRSequences(code);
for (const individualCode of codes) {
ret.push({
type: "ansi",
code: individualCode,
endCode: getEndCode(individualCode),
});
}
}
}
if (code) {
index += code.length;
continue;
}
}
const fullWidth = isFullwidthCodePoint(codePoint);
const character = String.fromCodePoint(codePoint);
ret.push({
type: "char",
value: character,
fullWidth,
});
index += character.length;
visible += fullWidth ? 2 : character.length;
if (visible >= endChar) {
break;
}
}
return ret;
}
//# sourceMappingURL=tokenize.js.map

View File

@@ -1,11 +0,0 @@
import { reduceAnsiCodes } from "./reduce.js";
/** Returns the combination of ANSI codes needed to undo the given ANSI codes */
export function undoAnsiCodes(codes) {
return reduceAnsiCodes(codes)
.reverse()
.map((code) => ({
...code,
code: code.endCode,
}));
}
//# sourceMappingURL=undo.js.map

View File

@@ -1,25 +0,0 @@
const path = require("path");
// Discriminated union: { isSupported: false } on non-darwin,
// { isSupported: true, ...nativeFns } on darwin. Cross-platform consumers
// (claude-cli-internal) require() unconditionally and narrow on isSupported.
if (process.platform !== "darwin") {
module.exports = { isSupported: false };
} else {
// COMPUTER_USE_INPUT_NODE_PATH: escape hatch for bundlers. Bun's --compile
// embeds the .node as an asset, not in a node_modules tree — __dirname is
// the exe dir and ../prebuilds/ doesn't exist. The consuming build bakes
// this var to the embedded asset's path. Unset → normal node_modules layout.
//
// key()/keys() dispatch enigo work onto DispatchQueue.main via
// dispatch2::run_on_main, then block a tokio worker on a channel. Under
// Electron (CFRunLoop drains the main queue) this works; under libuv
// (Node/bun) the main queue never drains and the promise hangs. Consumers
// running under libuv must pump CFRunLoop while key()/keys() are pending —
// e.g. claude-cli-internal borrows @ant/computer-use-swift's _drainMainRunLoop.
const native = require(
process.env.COMPUTER_USE_INPUT_NODE_PATH ??
path.resolve(__dirname, "../prebuilds/computer-use-input.node"),
);
module.exports = { isSupported: true, ...native };
}

View File

@@ -1,553 +0,0 @@
/**
* App category lookup for tiered CU permissions. Three categories land at a
* restricted tier instead of `"full"`:
*
* - **browser** → `"read"` tier — visible in screenshots, NO interaction.
* The model can read an already-open page but must use the Claude-in-Chrome
* MCP for navigation/clicking/typing.
* - **terminal** → `"click"` tier — visible + clickable, NO typing. The
* model can click a Run button or scroll test output in an IDE, but can't
* type into the integrated terminal. Use the Bash tool for shell work.
* - **trading** → `"read"` tier — same restrictions as browsers, but no
* CiC-MCP alternative exists. For platforms where a stray click can
* execute a trade or send a message to a counterparty.
*
* Uncategorized apps default to `"full"`. See `getDefaultTierForApp`.
*
* Identification is two-layered:
* 1. Bundle ID match (macOS-only; `InstalledApp.bundleId` is a
* CFBundleIdentifier and meaningless on Windows). Fast, exact, the
* primary mechanism while CU is darwin-gated.
* 2. Display-name substring match (cross-platform fallback). Catches
* unresolved requests ("Chrome" when Chrome isn't installed) AND will
* be the primary mechanism on Windows/Linux where there's no bundle ID.
* Windows-relevant names (PowerShell, cmd, Windows Terminal) are
* included now so they activate the moment the darwin gate lifts.
*
* Keep this file **import-free** (like sentinelApps.ts) — the renderer may
* import it via a package.json subpath export, and pulling in
* `@modelcontextprotocol/sdk` (a devDep) through the index → mcpServer chain
* would fail module resolution in Next.js. The `CuAppPermTier` type is
* duplicated as a string literal below rather than imported.
*/
export type DeniedCategory = "browser" | "terminal" | "trading";
/**
* Map a category to its hardcoded tier. Return-type is the string-literal
* union inline (this file is import-free; see header comment). The
* authoritative type is `CuAppPermTier` in types.ts — keep in sync.
*
* Not bijective — both `"browser"` and `"trading"` map to `"read"`. Copy
* that differs by category (the "use CiC" hint is browser-only) must check
* the category, not just the tier.
*/
export function categoryToTier(
category: DeniedCategory | null,
): "read" | "click" | "full" {
if (category === "browser" || category === "trading") return "read";
if (category === "terminal") return "click";
return "full";
}
// ─── Bundle-ID deny sets (macOS) ─────────────────────────────────────────
const BROWSER_BUNDLE_IDS: ReadonlySet<string> = new Set([
// Apple
"com.apple.Safari",
"com.apple.SafariTechnologyPreview",
// Google
"com.google.Chrome",
"com.google.Chrome.beta",
"com.google.Chrome.dev",
"com.google.Chrome.canary",
// Microsoft
"com.microsoft.edgemac",
"com.microsoft.edgemac.Beta",
"com.microsoft.edgemac.Dev",
"com.microsoft.edgemac.Canary",
// Mozilla
"org.mozilla.firefox",
"org.mozilla.firefoxdeveloperedition",
"org.mozilla.nightly",
// Chromium-based
"org.chromium.Chromium",
"com.brave.Browser",
"com.brave.Browser.beta",
"com.brave.Browser.nightly",
"com.operasoftware.Opera",
"com.operasoftware.OperaGX",
"com.operasoftware.OperaDeveloper",
"com.vivaldi.Vivaldi",
// The Browser Company
"company.thebrowser.Browser", // Arc
"company.thebrowser.dia", // Dia (agentic)
// Privacy-focused
"org.torproject.torbrowser",
"com.duckduckgo.macos.browser",
"ru.yandex.desktop.yandex-browser",
// Agentic / AI browsers — newer entrants with LLM integrations
"ai.perplexity.comet",
"com.sigmaos.sigmaos.macos", // SigmaOS
// Webkit-based misc
"com.kagi.kagimacOS", // Orion
]);
/**
* Terminals + IDEs with integrated terminals. Supersets
* `SHELL_ACCESS_BUNDLE_IDS` from sentinelApps.ts — terminals proceed to the
* approval dialog at tier "click", and the sentinel warning renders
* alongside the tier badge.
*/
const TERMINAL_BUNDLE_IDS: ReadonlySet<string> = new Set([
// Dedicated terminals
"com.apple.Terminal",
"com.googlecode.iterm2",
"dev.warp.Warp-Stable",
"dev.warp.Warp-Beta",
"com.github.wez.wezterm",
"org.alacritty",
"io.alacritty", // pre-v0.11.0 (renamed 2022-07) — kept for legacy installs
"net.kovidgoyal.kitty",
"co.zeit.hyper",
"com.mitchellh.ghostty",
"org.tabby",
"com.termius-dmg.mac", // Termius
// IDEs with integrated terminals — we can't distinguish "type in the
// editor" from "type in the integrated terminal" via screenshot+click.
// VS Code family
"com.microsoft.VSCode",
"com.microsoft.VSCodeInsiders",
"com.vscodium", // VSCodium
"com.todesktop.230313mzl4w4u92", // Cursor
"com.exafunction.windsurf", // Windsurf / Codeium
"dev.zed.Zed",
"dev.zed.Zed-Preview",
// JetBrains family (all have integrated terminals)
"com.jetbrains.intellij",
"com.jetbrains.intellij.ce",
"com.jetbrains.pycharm",
"com.jetbrains.pycharm.ce",
"com.jetbrains.WebStorm",
"com.jetbrains.CLion",
"com.jetbrains.goland",
"com.jetbrains.rubymine",
"com.jetbrains.PhpStorm",
"com.jetbrains.datagrip",
"com.jetbrains.rider",
"com.jetbrains.AppCode",
"com.jetbrains.rustrover",
"com.jetbrains.fleet",
"com.google.android.studio", // Android Studio (JetBrains-based)
// Other IDEs
"com.axosoft.gitkraken", // GitKraken has an integrated terminal panel. Also keeps the "kraken" trading-substring from miscategorizing it — bundle-ID wins.
"com.sublimetext.4",
"com.sublimetext.3",
"org.vim.MacVim",
"com.neovim.neovim",
"org.gnu.Emacs",
// Xcode's previous carve-out (full tier for Interface Builder / simulator)
// was reversed — at tier "click" IB and simulator taps still work (both are
// plain clicks) while the integrated terminal is blocked from keyboard input.
"com.apple.dt.Xcode",
"org.eclipse.platform.ide",
"org.netbeans.ide",
"com.microsoft.visual-studio", // Visual Studio for Mac
// AppleScript/automation execution surfaces — same threat as terminals:
// type(script) → key("cmd+r") runs arbitrary code. Added after #28011
// removed the osascript MCP server, making CU the only tool-call route
// to AppleScript.
"com.apple.ScriptEditor2",
"com.apple.Automator",
"com.apple.shortcuts",
]);
/**
* Trading / crypto platforms — granted at tier `"read"` so the agent can see
* balances and prices but can't click into an order, transfer, or IB chat.
* Bundle IDs populated from Homebrew cask `uninstall.quit` stanzas as they're
* verified; the name-substring fallback below is the primary check. Bloomberg
* Terminal has no native macOS build per their FAQ (web/Citrix only).
*
* Budgeting/accounting apps (Quicken, YNAB, QuickBooks, etc.) are NOT listed
* here — they default to tier `"full"`. The risk model for brokerage/crypto
* (a stray click can execute a trade) doesn't apply to budgeting apps; the
* Cowork system prompt carries the soft instruction to never execute trades
* or transfer money on the user's behalf.
*/
const TRADING_BUNDLE_IDS: ReadonlySet<string> = new Set([
// Verified via Homebrew quit/zap stanzas + mdls + electron-builder source.
// Trading
"com.webull.desktop.v1", // Webull (direct download, Qt)
"com.webull.trade.mac.v1", // Webull (Mac App Store)
"com.tastytrade.desktop",
"com.tradingview.tradingviewapp.desktop",
"com.fidelity.activetrader", // Fidelity Trader+ (new)
"com.fmr.activetrader", // Fidelity Active Trader Pro (legacy)
// Interactive Brokers TWS — install4j wrapper; Homebrew quit stanza is
// authoritative for this exact value but install4j IDs can drift across
// major versions — name-substring "trader workstation" is the fallback.
"com.install4j.5889-6375-8446-2021",
// Crypto
"com.binance.BinanceDesktop",
"com.electron.exodus",
// Electrum uses PyInstaller with bundle_identifier=None → defaults to
// org.pythonmac.unspecified.<AppName>. Confirmed in spesmilo/electrum
// source + Homebrew zap. IntuneBrew's "org.electrum.electrum" is a fork.
"org.pythonmac.unspecified.Electrum",
"com.ledger.live",
"io.trezor.TrezorSuite",
// No native macOS app (name-substring only): Schwab, E*TRADE, TradeStation,
// Robinhood, NinjaTrader, Coinbase, Kraken, Bloomberg. thinkorswim
// install4j ID drifts per-install — substring safer.
]);
// ─── Policy-deny (not a tier — cannot be granted at all) ─────────────────
//
// Streaming / ebook / music apps and a handful of publisher apps. These
// are auto-denied before the approval dialog — no tier can be granted.
// Rationale is copyright / content-control (the agent has no legitimate
// need to screenshot Netflix or click Play on Spotify).
//
// Sourced from the ACP CU-apps blocklist xlsx ("Full block" tab). See
// /tmp/extract_cu_blocklist.py for the extraction script.
const POLICY_DENIED_BUNDLE_IDS: ReadonlySet<string> = new Set([
// Verified via Homebrew quit/zap + mdls /System/Applications + IntuneBrew.
// Apple built-ins
"com.apple.TV",
"com.apple.Music",
"com.apple.iBooksX",
"com.apple.podcasts",
// Music
"com.spotify.client",
"com.amazon.music",
"com.tidal.desktop",
"com.deezer.deezer-desktop",
"com.pandora.desktop",
"com.electron.pocket-casts", // direct-download Electron wrapper
"au.com.shiftyjelly.PocketCasts", // Mac App Store
// Video
"tv.plex.desktop",
"tv.plex.htpc",
"tv.plex.plexamp",
"com.amazon.aiv.AIVApp", // Prime Video (iOS-on-Apple-Silicon)
// Ebooks
"net.kovidgoyal.calibre",
"com.amazon.Kindle", // legacy desktop, discontinued
"com.amazon.Lassen", // current Mac App Store (iOS-on-Mac)
"com.kobo.desktop.Kobo",
// No native macOS app (name-substring only): Netflix, Disney+, Hulu,
// HBO Max, Peacock, Paramount+, YouTube, Crunchyroll, Tubi, Vudu,
// Audible, Reddit, NYTimes. Their iOS apps don't opt into iPad-on-Mac.
]);
const POLICY_DENIED_NAME_SUBSTRINGS: readonly string[] = [
// Video streaming
"netflix",
"disney+",
"hulu",
"prime video",
"apple tv",
"peacock",
"paramount+",
// "plex" is too generic — would match "Perplexity". Covered by
// tv.plex.* bundle IDs on macOS.
"tubi",
"crunchyroll",
"vudu",
// E-readers / audiobooks
"kindle",
"apple books",
"kobo",
"play books",
"calibre",
"libby",
"readium",
"audible",
"libro.fm",
"speechify",
// Music
"spotify",
"apple music",
"amazon music",
"youtube music",
"tidal",
"deezer",
"pandora",
"pocket casts",
// Publisher / social apps (from the same blocklist tab)
"naver",
"reddit",
"sony music",
"vegas pro",
"pitchfork",
"economist",
"nytimes",
// Skipped (too generic for substring matching — need bundle ID):
// HBO Max / Max, YouTube (non-Music), Nook, Sony Catalyst, Wired
];
/**
* Policy-level auto-deny. Unlike `userDeniedBundleIds` (per-user Settings
* page), this is baked into the build. `buildAccessRequest` strips these
* before the approval dialog with "blocked by policy" guidance; the agent
* is told to not retry.
*/
export function isPolicyDenied(
bundleId: string | undefined,
displayName: string,
): boolean {
if (bundleId && POLICY_DENIED_BUNDLE_IDS.has(bundleId)) return true;
const lower = displayName.toLowerCase();
for (const sub of POLICY_DENIED_NAME_SUBSTRINGS) {
if (lower.includes(sub)) return true;
}
return false;
}
export function getDeniedCategory(bundleId: string): DeniedCategory | null {
if (BROWSER_BUNDLE_IDS.has(bundleId)) return "browser";
if (TERMINAL_BUNDLE_IDS.has(bundleId)) return "terminal";
if (TRADING_BUNDLE_IDS.has(bundleId)) return "trading";
return null;
}
// ─── Display-name fallback (cross-platform) ──────────────────────────────
/**
* Lowercase substrings checked against the requested display name. Catches:
* - Unresolved requests (app not installed, Spotlight miss)
* - Future Windows/Linux support where bundleId is meaningless
*
* Matched via `.includes()` on `name.toLowerCase()`. Entries are ordered
* by specificity (more-specific first is irrelevant since we return on
* first match, but groupings are by category for readability).
*/
const BROWSER_NAME_SUBSTRINGS: readonly string[] = [
"safari",
"chrome",
"firefox",
"microsoft edge",
"brave",
"opera",
"vivaldi",
"chromium",
// Arc/Dia: the canonical display name is just "Arc"/"Dia" — too short for
// substring matching (false-positives: "Arcade", "Diagram"). Covered by
// bundle ID on macOS. The "... browser" entries below catch natural-language
// phrasings ("the arc browser") but NOT the canonical short name.
"arc browser",
"tor browser",
"duckduckgo",
"yandex",
"orion browser",
// Agentic / AI browsers
"comet", // Perplexity's browser — "Comet" substring risks false positives
// but leaving for now; "comet" in an app name is rare
"sigmaos",
"dia browser",
];
const TERMINAL_NAME_SUBSTRINGS: readonly string[] = [
// macOS / cross-platform terminals
"terminal", // catches Terminal, Windows Terminal (NOT iTerm — separate entry)
"iterm",
"wezterm",
"alacritty",
"kitty",
"ghostty",
"tabby",
"termius",
// AppleScript runners — see bundle-ID comment above. "shortcuts" is too
// generic for substring matching (many apps have "shortcuts" in the name);
// covered by bundle ID only, like warp/hyper.
"script editor",
"automator",
// NOTE: "warp" and "hyper" are too generic for substring matching —
// they'd false-positive on "Warpaint" or "Hyperion". Covered by bundle ID
// (dev.warp.Warp-Stable, co.zeit.hyper) for macOS; Windows exe-name
// matching can be added when Windows CU ships.
// Windows shells (activate when the darwin gate lifts)
"powershell",
"cmd.exe",
"command prompt",
"git bash",
"conemu",
"cmder",
// IDEs (VS Code family)
"visual studio code",
"visual studio", // catches VS for Mac + Windows
"vscode",
"vs code",
"vscodium",
"cursor", // Cursor IDE — "cursor" is generic but IDE is the only common app
"windsurf",
// Zed: display name is just "Zed" — too short for substring matching
// (false-positives). Covered by bundle ID (dev.zed.Zed) on macOS.
// IDEs (JetBrains family)
"intellij",
"pycharm",
"webstorm",
"clion",
"goland",
"rubymine",
"phpstorm",
"datagrip",
"rider",
"appcode",
"rustrover",
"fleet",
"android studio",
// Other IDEs
"sublime text",
"macvim",
"neovim",
"emacs",
"xcode",
"eclipse",
"netbeans",
];
const TRADING_NAME_SUBSTRINGS: readonly string[] = [
// Trading — brokerage apps. Sourced from the ACP CU-apps blocklist xlsx
// ("Read Only" tab). Name-substring safe for proper nouns below; generic
// names (IG, Delta, HTX) are skipped and need bundle-ID matching once
// verified.
"bloomberg",
"ameritrade",
"thinkorswim",
"schwab",
"fidelity",
"e*trade",
"interactive brokers",
"trader workstation", // Interactive Brokers TWS
"tradestation",
"webull",
"robinhood",
"tastytrade",
"ninjatrader",
"tradingview",
"moomoo",
"tradezero",
"prorealtime",
"plus500",
"saxotrader",
"oanda",
"metatrader",
"forex.com",
"avaoptions",
"ctrader",
"jforex",
"iq option",
"olymp trade",
"binomo",
"pocket option",
"raceoption",
"expertoption",
"quotex",
"naga",
"morgan stanley",
"ubs neo",
"eikon", // Thomson Reuters / LSEG Workspace
// Crypto — exchanges, wallets, portfolio trackers
"coinbase",
"kraken",
"binance",
"okx",
"bybit",
// "gate.io" is too generic — the ".io" TLD suffix is common in app names
// (e.g., "Draw.io"). Needs bundle-ID matching once verified.
"phemex",
"stormgain",
"crypto.com",
// "exodus" is too generic — it's a common noun and would match unrelated
// apps/games. Needs bundle-ID matching once verified.
"electrum",
"ledger live",
"trezor",
"guarda",
"atomic wallet",
"bitpay",
"bisq",
"koinly",
"cointracker",
"blockfi",
"stripe cli",
// Crypto games / metaverse (same trade-execution risk model)
"decentraland",
"axie infinity",
"gods unchained",
];
/**
* Display-name substring match. Called when bundle-ID resolution returned
* nothing (`resolved === undefined`) or when no bundle-ID deny-list entry
* matched. Returns the category for the first matching substring, or null.
*
* Case-insensitive, substring — so `"Google Chrome"`, `"chrome"`, and
* `"Chrome Canary"` all match the `"chrome"` entry.
*/
export function getDeniedCategoryByDisplayName(
name: string,
): DeniedCategory | null {
const lower = name.toLowerCase();
// Trading first — proper-noun-only set, most specific. "Bloomberg Terminal"
// contains "terminal" and would miscategorize if TERMINAL_NAME_SUBSTRINGS
// ran first.
for (const sub of TRADING_NAME_SUBSTRINGS) {
if (lower.includes(sub)) return "trading";
}
for (const sub of BROWSER_NAME_SUBSTRINGS) {
if (lower.includes(sub)) return "browser";
}
for (const sub of TERMINAL_NAME_SUBSTRINGS) {
if (lower.includes(sub)) return "terminal";
}
return null;
}
/**
* Combined check — bundle ID first (exact, fast), then display-name
* fallback. This is the function tool-call handlers should use.
*
* `bundleId` may be undefined (unresolved request — model asked for an app
* that isn't installed or Spotlight didn't find). In that case only the
* display-name check runs.
*/
export function getDeniedCategoryForApp(
bundleId: string | undefined,
displayName: string,
): DeniedCategory | null {
if (bundleId) {
const byId = getDeniedCategory(bundleId);
if (byId) return byId;
}
return getDeniedCategoryByDisplayName(displayName);
}
/**
* Default tier for an app at grant time. Wraps `getDeniedCategoryForApp` +
* `categoryToTier`. Browsers → `"read"`, terminals/IDEs → `"click"`,
* everything else → `"full"`.
*
* Called by `buildAccessRequest` to populate `ResolvedAppRequest.proposedTier`
* before the approval dialog shows.
*/
export function getDefaultTierForApp(
bundleId: string | undefined,
displayName: string,
): "read" | "click" | "full" {
return categoryToTier(getDeniedCategoryForApp(bundleId, displayName));
}
export const _test = {
BROWSER_BUNDLE_IDS,
TERMINAL_BUNDLE_IDS,
TRADING_BUNDLE_IDS,
POLICY_DENIED_BUNDLE_IDS,
BROWSER_NAME_SUBSTRINGS,
TERMINAL_NAME_SUBSTRINGS,
TRADING_NAME_SUBSTRINGS,
POLICY_DENIED_NAME_SUBSTRINGS,
};

View File

@@ -1,108 +0,0 @@
/**
* Port of the API's image transcoder target-size algorithm. Pre-sizing
* screenshots to this function's output means the API's early-return fires
* (tokens ≤ max) and the image is NOT resized server-side — so the model
* sees exactly the dimensions in `ScreenshotResult.width/height` and
* `scaleCoord` stays coherent.
*
* Rust reference: api/api/image_transcoder/rust_transcoder/src/utils/resize.rs
* Sibling TS port: apps/claude-browser-use/src/utils/imageResize.ts (identical
* algorithm, lives in the Chrome extension tree — not a shared package).
*
* See COORDINATES.md for why this matters for click accuracy.
*/
export interface ResizeParams {
pxPerToken: number;
maxTargetPx: number;
maxTargetTokens: number;
}
/**
* Production defaults — match `resize.rs:160-164` and Chrome's
* `CDPService.ts:638-642`. Vision encoder uses 28px tiles; 1568 is both
* the long-edge cap (56 tiles) AND the token budget.
*/
export const API_RESIZE_PARAMS: ResizeParams = {
pxPerToken: 28,
maxTargetPx: 1568,
maxTargetTokens: 1568,
};
/** ceil(px / pxPerToken). Matches resize.rs:74-76 (which uses integer ceil-div). */
export function nTokensForPx(px: number, pxPerToken: number): number {
return Math.floor((px - 1) / pxPerToken) + 1;
}
function nTokensForImg(
width: number,
height: number,
pxPerToken: number,
): number {
return nTokensForPx(width, pxPerToken) * nTokensForPx(height, pxPerToken);
}
/**
* Binary-search along the width dimension for the largest image that:
* - preserves the input aspect ratio
* - has long edge ≤ maxTargetPx
* - has ceil(w/pxPerToken) × ceil(h/pxPerToken) ≤ maxTargetTokens
*
* Returns [width, height]. No-op if input already satisfies all three.
*
* The long-edge constraint alone (what we used to use) is insufficient on
* squarer-than-16:9 displays: 1568×1014 (MBP 16" AR) is 56×37 = 2072 tokens,
* over budget, and gets server-resized to 1372×887 — model then clicks in
* 1372-space but scaleCoord assumed 1568-space → ~14% coord error.
*
* Matches resize.rs:91-155 exactly (verified against its test vectors).
*/
export function targetImageSize(
width: number,
height: number,
params: ResizeParams,
): [number, number] {
const { pxPerToken, maxTargetPx, maxTargetTokens } = params;
if (
width <= maxTargetPx &&
height <= maxTargetPx &&
nTokensForImg(width, height, pxPerToken) <= maxTargetTokens
) {
return [width, height];
}
// Normalize to landscape for the search; transpose result back.
if (height > width) {
const [w, h] = targetImageSize(height, width, params);
return [h, w];
}
const aspectRatio = width / height;
// Loop invariant: lowerBoundWidth is always valid, upperBoundWidth is
// always invalid. ~12 iterations for a 4000px image.
let upperBoundWidth = width;
let lowerBoundWidth = 1;
for (;;) {
if (lowerBoundWidth + 1 === upperBoundWidth) {
return [
lowerBoundWidth,
Math.max(Math.round(lowerBoundWidth / aspectRatio), 1),
];
}
const middleWidth = Math.floor((lowerBoundWidth + upperBoundWidth) / 2);
const middleHeight = Math.max(Math.round(middleWidth / aspectRatio), 1);
if (
middleWidth <= maxTargetPx &&
nTokensForImg(middleWidth, middleHeight, pxPerToken) <= maxTargetTokens
) {
lowerBoundWidth = middleWidth;
} else {
upperBoundWidth = middleWidth;
}
}
}

View File

@@ -1,69 +0,0 @@
export type {
ComputerExecutor,
DisplayGeometry,
FrontmostApp,
InstalledApp,
ResolvePrepareCaptureResult,
RunningApp,
ScreenshotResult,
} from "./executor.js";
export type {
AppGrant,
CuAppPermTier,
ComputerUseHostAdapter,
ComputerUseOverrides,
ComputerUseSessionContext,
CoordinateMode,
CuGrantFlags,
CuPermissionRequest,
CuPermissionResponse,
CuSubGates,
CuTeachPermissionRequest,
Logger,
ResolvedAppRequest,
ScreenshotDims,
TeachStepRequest,
TeachStepResult,
} from "./types.js";
export { DEFAULT_GRANT_FLAGS } from "./types.js";
export {
SENTINEL_BUNDLE_IDS,
getSentinelCategory,
} from "./sentinelApps.js";
export type { SentinelCategory } from "./sentinelApps.js";
export {
categoryToTier,
getDefaultTierForApp,
getDeniedCategory,
getDeniedCategoryByDisplayName,
getDeniedCategoryForApp,
isPolicyDenied,
} from "./deniedApps.js";
export type { DeniedCategory } from "./deniedApps.js";
export { isSystemKeyCombo, normalizeKeySequence } from "./keyBlocklist.js";
export { ALL_SUB_GATES_OFF, ALL_SUB_GATES_ON } from "./subGates.js";
export { API_RESIZE_PARAMS, targetImageSize } from "./imageResize.js";
export type { ResizeParams } from "./imageResize.js";
export { defersLockAcquire, handleToolCall } from "./toolCalls.js";
export type {
CuCallTelemetry,
CuCallToolResult,
CuErrorKind,
} from "./toolCalls.js";
export { bindSessionContext, createComputerUseMcpServer } from "./mcpServer.js";
export { buildComputerUseTools } from "./tools.js";
export {
comparePixelAtLocation,
validateClickTarget,
} from "./pixelCompare.js";
export type { CropRawPatchFn, PixelCompareResult } from "./pixelCompare.js";

View File

@@ -1,153 +0,0 @@
/**
* Key combos that cross app boundaries or terminate processes. Gated behind
* the `systemKeyCombos` grant flag. When that flag is off, the `key` tool
* rejects these and returns a tool error telling the model to request the
* flag; all other combos work normally.
*
* Matching is canonicalized: every modifier alias the Rust executor accepts
* collapses to one canonical name. Without this, `command+q` / `meta+q` /
* `cmd+alt+escape` bypass the gate — see keyBlocklist.test.ts for the three
* bypass forms and the Rust parity check that catches future alias drift.
*/
/**
* Every modifier alias enigo_wrap.rs accepts (two copies: :351-359, :564-572),
* mapped to one canonical per Key:: variant. Left/right variants collapse —
* the blocklist doesn't distinguish which Ctrl.
*
* Canonical names are Rust's own variant names lowercased. Blocklist entries
* below use ONLY these. "meta" reads odd for Cmd+Q but it's honest: Rust
* sends Key::Meta, which is Cmd on darwin and Win on win32.
*/
const CANONICAL_MODIFIER: Readonly<Record<string, string>> = {
// Key::Meta — "meta"|"super"|"command"|"cmd"|"windows"|"win"
meta: "meta",
super: "meta",
command: "meta",
cmd: "meta",
windows: "meta",
win: "meta",
// Key::Control + LControl + RControl
ctrl: "ctrl",
control: "ctrl",
lctrl: "ctrl",
lcontrol: "ctrl",
rctrl: "ctrl",
rcontrol: "ctrl",
// Key::Shift + LShift + RShift
shift: "shift",
lshift: "shift",
rshift: "shift",
// Key::Alt and Key::Option — distinct Rust variants but same keycode on
// darwin (kVK_Option). Collapse: cmd+alt+escape and cmd+option+escape
// both Force Quit.
alt: "alt",
option: "alt",
};
/** Sort order for canonicals. ctrl < alt < shift < meta. */
const MODIFIER_ORDER = ["ctrl", "alt", "shift", "meta"];
/**
* Canonical-form entries only. Every modifier must be a CANONICAL_MODIFIER
* *value* (not key), modifiers must be in MODIFIER_ORDER, non-modifier last.
* The self-consistency test enforces this.
*/
const BLOCKED_DARWIN = new Set([
"meta+q", // Cmd+Q — quit frontmost app
"shift+meta+q", // Cmd+Shift+Q — log out
"alt+meta+escape", // Cmd+Option+Esc — Force Quit dialog
"meta+tab", // Cmd+Tab — app switcher
"meta+space", // Cmd+Space — Spotlight
"ctrl+meta+q", // Ctrl+Cmd+Q — lock screen
]);
const BLOCKED_WIN32 = new Set([
"ctrl+alt+delete", // Secure Attention Sequence
"alt+f4", // close window
"alt+tab", // window switcher
"meta+l", // Win+L — lock
"meta+d", // Win+D — show desktop
]);
/**
* Partition into sorted-canonical modifiers and non-modifier keys.
* Shared by normalizeKeySequence (join for display) and isSystemKeyCombo
* (check mods+each-key to catch the cmd+q+a suffix bypass).
*/
function partitionKeys(seq: string): { mods: string[]; keys: string[] } {
const parts = seq
.toLowerCase()
.split("+")
.map((p) => p.trim())
.filter(Boolean);
const mods: string[] = [];
const keys: string[] = [];
for (const p of parts) {
const canonical = CANONICAL_MODIFIER[p];
if (canonical !== undefined) {
mods.push(canonical);
} else {
keys.push(p);
}
}
// Dedupe: "cmd+command+q" → "meta+q", not "meta+meta+q".
const uniqueMods = [...new Set(mods)];
uniqueMods.sort(
(a, b) => MODIFIER_ORDER.indexOf(a) - MODIFIER_ORDER.indexOf(b),
);
return { mods: uniqueMods, keys };
}
/**
* Normalize "Cmd + Shift + Q" → "shift+meta+q": lowercase, trim, alias →
* canonical, dedupe, sort modifiers, non-modifiers last.
*/
export function normalizeKeySequence(seq: string): string {
const { mods, keys } = partitionKeys(seq);
return [...mods, ...keys].join("+");
}
/**
* True if the sequence would fire a blocked OS shortcut.
*
* Checks mods + EACH non-modifier key individually, not just the full
* joined string. `cmd+q+a` → Rust presses Cmd, then Q (Cmd+Q fires here),
* then A. Exact-match against "meta+q+a" misses; checking "meta+q" and
* "meta+a" separately catches the Q.
*
* Modifiers-only sequences ("cmd+shift") are checked as-is — no key to
* pair with, and no blocklist entry is modifier-only, so this is a no-op
* that falls through to false. Covers the click-modifier case where
* `left_click(text="cmd")` is legitimate.
*/
export function isSystemKeyCombo(
seq: string,
platform: "darwin" | "win32",
): boolean {
const blocklist = platform === "darwin" ? BLOCKED_DARWIN : BLOCKED_WIN32;
const { mods, keys } = partitionKeys(seq);
const prefix = mods.length > 0 ? mods.join("+") + "+" : "";
// No non-modifier keys (e.g. "cmd+shift" as click-modifiers) — check the
// whole thing. Never matches (no blocklist entry is modifier-only) but
// keeps the contract simple: every call reaches a .has().
if (keys.length === 0) {
return blocklist.has(mods.join("+"));
}
// mods + each key. Any hit blocks the whole sequence.
for (const key of keys) {
if (blocklist.has(prefix + key)) {
return true;
}
}
return false;
}
export const _test = {
CANONICAL_MODIFIER,
BLOCKED_DARWIN,
BLOCKED_WIN32,
MODIFIER_ORDER,
};

View File

@@ -1,313 +0,0 @@
/**
* MCP server factory + session-context binder.
*
* Two entry points:
*
* `bindSessionContext` — the wrapper closure. Takes a `ComputerUseSessionContext`
* (getters + callbacks backed by host session state), returns a dispatcher.
* Reusable by both the MCP CallTool handler here AND Cowork's
* `InternalServerDefinition.handleToolCall` (which doesn't go through MCP).
* This replaces the duplicated wrapper closures in apps/desktop/…/serverDef.ts
* and the Claude Code CLI's CU host wrapper — both did the same thing: build `ComputerUseOverrides`
* fresh from getters, call `handleToolCall`, stash screenshot, merge permissions.
*
* `createComputerUseMcpServer` — the Server object. When `context` is provided,
* the CallTool handler is real (uses `bindSessionContext`). When not, it's the
* legacy stub that returns a not-wired error. The tool-schema ListTools handler
* is the same either way.
*/
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import type { ScreenshotResult } from "./executor.js";
import type { CuCallToolResult } from "./toolCalls.js";
import {
defersLockAcquire,
handleToolCall,
resetMouseButtonHeld,
} from "./toolCalls.js";
import { buildComputerUseTools } from "./tools.js";
import type {
AppGrant,
ComputerUseHostAdapter,
ComputerUseOverrides,
ComputerUseSessionContext,
CoordinateMode,
CuGrantFlags,
CuPermissionResponse,
} from "./types.js";
import { DEFAULT_GRANT_FLAGS } from "./types.js";
const DEFAULT_LOCK_HELD_MESSAGE =
"Another Claude session is currently using the computer. Wait for that " +
"session to finish, or find a non-computer-use approach.";
/**
* Dedupe `granted` into `existing` on bundleId, spread truthy-only flags over
* defaults+existing. Truthy-only: a subsequent `request_access` that doesn't
* request clipboard can't revoke an earlier clipboard grant — revocation lives
* in a Settings page, not here.
*
* Same merge both hosts implemented independently today.
*/
function mergePermissionResponse(
existing: readonly AppGrant[],
existingFlags: CuGrantFlags,
response: CuPermissionResponse,
): { apps: AppGrant[]; flags: CuGrantFlags } {
const seen = new Set(existing.map((a) => a.bundleId));
const apps = [
...existing,
...response.granted.filter((g) => !seen.has(g.bundleId)),
];
const truthyFlags = Object.fromEntries(
Object.entries(response.flags).filter(([, v]) => v === true),
);
const flags: CuGrantFlags = {
...DEFAULT_GRANT_FLAGS,
...existingFlags,
...truthyFlags,
};
return { apps, flags };
}
/**
* Bind session state to a reusable dispatcher. The returned function is the
* wrapper closure: async lock gate → build overrides fresh → `handleToolCall`
* → stash screenshot → strip piggybacked fields.
*
* The last-screenshot blob is held in a closure cell here (not on `ctx`), so
* hosts don't need to guarantee `ctx` object identity across calls — they just
* need to hold onto the returned dispatcher. Cowork caches per
* `InternalServerContext` in a WeakMap; the CLI host constructs once at server creation.
*/
export function bindSessionContext(
adapter: ComputerUseHostAdapter,
coordinateMode: CoordinateMode,
ctx: ComputerUseSessionContext,
): (name: string, args: unknown) => Promise<CuCallToolResult> {
const { logger, serverName } = adapter;
// Screenshot blob persists here across calls — NOT on `ctx`. Hosts hold
// onto the returned dispatcher; that's the identity that matters.
let lastScreenshot: ScreenshotResult | undefined;
const wrapPermission = ctx.onPermissionRequest
? async (
req: Parameters<NonNullable<typeof ctx.onPermissionRequest>>[0],
signal: AbortSignal,
): Promise<CuPermissionResponse> => {
const response = await ctx.onPermissionRequest!(req, signal);
const { apps, flags } = mergePermissionResponse(
ctx.getAllowedApps(),
ctx.getGrantFlags(),
response,
);
logger.debug(
`[${serverName}] permission result: granted=${response.granted.length} denied=${response.denied.length}`,
);
ctx.onAllowedAppsChanged?.(apps, flags);
return response;
}
: undefined;
const wrapTeachPermission = ctx.onTeachPermissionRequest
? async (
req: Parameters<NonNullable<typeof ctx.onTeachPermissionRequest>>[0],
signal: AbortSignal,
): Promise<CuPermissionResponse> => {
const response = await ctx.onTeachPermissionRequest!(req, signal);
logger.debug(
`[${serverName}] teach permission result: granted=${response.granted.length} denied=${response.denied.length}`,
);
// Teach doesn't request grant flags — preserve existing.
const { apps } = mergePermissionResponse(
ctx.getAllowedApps(),
ctx.getGrantFlags(),
response,
);
ctx.onAllowedAppsChanged?.(apps, {
...DEFAULT_GRANT_FLAGS,
...ctx.getGrantFlags(),
});
return response;
}
: undefined;
return async (name, args) => {
// ─── Async lock gate ─────────────────────────────────────────────────
// Replaces the sync Gate-3 in `handleToolCall` — we pass
// `checkCuLock: undefined` below so it no-ops. Hosts with
// cross-process locks (O_EXCL file) await the real primitive here
// instead of pre-computing + feeding a fake sync result.
if (ctx.checkCuLock) {
const lock = await ctx.checkCuLock();
if (lock.holder !== undefined && !lock.isSelf) {
const text =
ctx.formatLockHeldMessage?.(lock.holder) ?? DEFAULT_LOCK_HELD_MESSAGE;
return {
content: [{ type: "text", text }],
isError: true,
telemetry: { error_kind: "cu_lock_held" },
};
}
if (lock.holder === undefined && !defersLockAcquire(name)) {
await ctx.acquireCuLock?.();
// Re-check: the awaits above yield the microtask queue, so another
// session's check+acquire can interleave with ours. Hosts where
// acquire is a no-op when already held (Cowork's CuLockManager) give
// no signal that we lost — verify we're now the holder before
// proceeding. The CLI's O_EXCL file lock would surface this as a throw from
// acquire instead; this re-check is a belt-and-suspenders for that
// path too.
const recheck = await ctx.checkCuLock();
if (recheck.holder !== undefined && !recheck.isSelf) {
const text =
ctx.formatLockHeldMessage?.(recheck.holder) ??
DEFAULT_LOCK_HELD_MESSAGE;
return {
content: [{ type: "text", text }],
isError: true,
telemetry: { error_kind: "cu_lock_held" },
};
}
// Fresh holder → any prior session's mouseButtonHeld is stale.
// Mirrors what Gate-3 does on the acquire branch. After the
// re-check so we only clear module state when we actually won.
resetMouseButtonHeld();
}
}
// ─── Build overrides fresh ───────────────────────────────────────────
// Blob-first; dims-fallback with base64:"" when the closure cell is
// unset (cross-respawn). scaleCoord reads dims; pixelCompare sees "" →
// isEmpty → skip.
const dimsFallback = lastScreenshot
? undefined
: ctx.getLastScreenshotDims?.();
// Per-call AbortController for dialog dismissal. Aborted in `finally` —
// if handleToolCall finishes (MCP timeout, throw) before the user
// answers, the host's dialog handler sees the abort and tears down.
const dialogAbort = new AbortController();
const overrides: ComputerUseOverrides = {
allowedApps: [...ctx.getAllowedApps()],
grantFlags: ctx.getGrantFlags(),
userDeniedBundleIds: ctx.getUserDeniedBundleIds(),
coordinateMode,
selectedDisplayId: ctx.getSelectedDisplayId(),
displayPinnedByModel: ctx.getDisplayPinnedByModel?.(),
displayResolvedForApps: ctx.getDisplayResolvedForApps?.(),
lastScreenshot:
lastScreenshot ??
(dimsFallback ? { ...dimsFallback, base64: "" } : undefined),
onPermissionRequest: wrapPermission
? (req) => wrapPermission(req, dialogAbort.signal)
: undefined,
onTeachPermissionRequest: wrapTeachPermission
? (req) => wrapTeachPermission(req, dialogAbort.signal)
: undefined,
onAppsHidden: ctx.onAppsHidden,
getClipboardStash: ctx.getClipboardStash,
onClipboardStashChanged: ctx.onClipboardStashChanged,
onResolvedDisplayUpdated: ctx.onResolvedDisplayUpdated,
onDisplayPinned: ctx.onDisplayPinned,
onDisplayResolvedForApps: ctx.onDisplayResolvedForApps,
onTeachModeActivated: ctx.onTeachModeActivated,
onTeachStep: ctx.onTeachStep,
onTeachWorking: ctx.onTeachWorking,
getTeachModeActive: ctx.getTeachModeActive,
// Undefined → handleToolCall's sync Gate-3 no-ops. The async gate
// above already ran.
checkCuLock: undefined,
acquireCuLock: undefined,
isAborted: ctx.isAborted,
};
logger.debug(
`[${serverName}] tool=${name} allowedApps=${overrides.allowedApps.length} coordMode=${coordinateMode}`,
);
// ─── Dispatch ────────────────────────────────────────────────────────
try {
const result = await handleToolCall(adapter, name, args, overrides);
if (result.screenshot) {
lastScreenshot = result.screenshot;
const { base64: _blob, ...dims } = result.screenshot;
logger.debug(`[${serverName}] screenshot dims: ${JSON.stringify(dims)}`);
ctx.onScreenshotCaptured?.(dims);
}
return result;
} finally {
dialogAbort.abort();
}
};
}
export function createComputerUseMcpServer(
adapter: ComputerUseHostAdapter,
coordinateMode: CoordinateMode,
context?: ComputerUseSessionContext,
): Server {
const { serverName, logger } = adapter;
const server = new Server(
{ name: serverName, version: "0.1.3" },
{ capabilities: { tools: {}, logging: {} } },
);
const tools = buildComputerUseTools(
adapter.executor.capabilities,
coordinateMode,
);
server.setRequestHandler(ListToolsRequestSchema, async () =>
adapter.isDisabled() ? { tools: [] } : { tools },
);
if (context) {
const dispatch = bindSessionContext(adapter, coordinateMode, context);
server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<CallToolResult> => {
const { screenshot: _s, telemetry: _t, ...result } = await dispatch(
request.params.name,
request.params.arguments ?? {},
);
return result;
},
);
return server;
}
// Legacy: no context → stub handler. Reached only if something calls the
// server over MCP transport WITHOUT going through a binder (a wiring
// regression). Clear error instead of silent failure.
server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<CallToolResult> => {
logger.warn(
`[${serverName}] tool call "${request.params.name}" reached the stub handler — no session context bound. Per-session state unavailable.`,
);
return {
content: [
{
type: "text",
text: "This computer-use server instance is not wired to a session. Per-session app permissions are not available on this code path.",
},
],
isError: true,
};
},
);
return server;
}

View File

@@ -1,171 +0,0 @@
/**
* Staleness guard ported from the Vercept acquisition.
*
* Compares the model's last-seen screenshot against a fresh-right-now
* screenshot at the click target, so the model never clicks pixels it hasn't
* seen. If the 9×9 patch around the target differs, the click is aborted and
* the model is told to re-screenshot. This is NOT a popup detector.
*
* Semantics preserved exactly:
* - Skip on no `lastScreenshot` (cold start) — click proceeds.
* - Skip on any internal error (crop throws, screenshot fails, etc.) —
* click proceeds. Validation failure must never block the action.
* - 9×9 exact byte equality on raw pixel bytes. No fuzzing, no tolerance.
* - Compare in percentage coords so Retina scale doesn't matter.
*
* JPEG decode + crop is INJECTED via `ComputerUseHostAdapter.cropRawPatch`.
* The original used `sharp` (LGPL, native `.node` addon); we inject Electron's
* `nativeImage` (Chromium decoders, BSD, nothing to bundle) from the host, so
* this package never imports it — the crop is a function parameter.
*/
import type { ScreenshotResult } from "./executor.js";
import type { Logger } from "./types.js";
/** Injected by the host. See `ComputerUseHostAdapter.cropRawPatch`. */
export type CropRawPatchFn = (
jpegBase64: string,
rect: { x: number; y: number; width: number; height: number },
) => Buffer | null;
/** 9×9 is empirically the sweet spot — large enough to catch a tooltip
* appearing, small enough to not false-positive on surrounding animation.
**/
const DEFAULT_GRID_SIZE = 9;
export interface PixelCompareResult {
/** true → click may proceed. false → patch changed, abort the click. */
valid: boolean;
/** true → validation did not run (cold start, sub-gate off, or internal
* error). The caller MUST treat this identically to `valid: true`. */
skipped: boolean;
/** Populated when valid === false. Returned to the model verbatim. */
warning?: string;
}
/**
* Compute the crop rect for a patch centered on (xPercent, yPercent).
*
* Dimensions come from ScreenshotResult.width/height (physical pixels). Both
* screenshots have the same dimensions (same display, consecutive captures),
* so the rect is the same for both.
*/
function computeCropRect(
imgW: number,
imgH: number,
xPercent: number,
yPercent: number,
gridSize: number,
): { x: number; y: number; width: number; height: number } | null {
if (!imgW || !imgH) return null;
const clampedX = Math.max(0, Math.min(100, xPercent));
const clampedY = Math.max(0, Math.min(100, yPercent));
const centerX = Math.round((clampedX / 100.0) * imgW);
const centerY = Math.round((clampedY / 100.0) * imgH);
const halfGrid = Math.floor(gridSize / 2);
const cropX = Math.max(0, centerX - halfGrid);
const cropY = Math.max(0, centerY - halfGrid);
const cropW = Math.min(gridSize, imgW - cropX);
const cropH = Math.min(gridSize, imgH - cropY);
if (cropW <= 0 || cropH <= 0) return null;
return { x: cropX, y: cropY, width: cropW, height: cropH };
}
/**
* Compare the same patch location between two screenshots.
*
* @returns true when the raw pixel bytes are identical. false on any
* difference, or on any internal error (the caller treats an error here as
* `skipped`, so the false is harmless).
*/
export function comparePixelAtLocation(
crop: CropRawPatchFn,
lastScreenshot: ScreenshotResult,
freshScreenshot: ScreenshotResult,
xPercent: number,
yPercent: number,
gridSize: number = DEFAULT_GRID_SIZE,
): boolean {
// Both screenshots are of the same display — use the fresh one's
// dimensions (less likely to be stale than last's).
const rect = computeCropRect(
freshScreenshot.width,
freshScreenshot.height,
xPercent,
yPercent,
gridSize,
);
if (!rect) return false;
const patch1 = crop(lastScreenshot.base64, rect);
const patch2 = crop(freshScreenshot.base64, rect);
if (!patch1 || !patch2) return false;
// Direct buffer equality. Note: nativeImage.toBitmap() gives BGRA, sharp's
// .raw() gave RGB.
// Doesn't matter — we're comparing two same-format buffers for equality.
return patch1.equals(patch2);
}
/**
* Battle-tested click-target validation ported from the Vercept acquisition,
* with the fresh-screenshot capture delegated to the caller (we don't have
* a global `SystemActions.takeScreenshot()` — the executor is injected).
*
* Skip conditions (any of these → `{ valid: true, skipped: true }`):
* - `lastScreenshot` is undefined (cold start).
* - `takeFreshScreenshot()` throws or returns null.
* - Injected crop function returns null (decode failure).
* - Any other exception.
*
* The caller decides whether to invoke this at all (sub-gate check lives
* in toolCalls.ts, not here).
*/
export async function validateClickTarget(
crop: CropRawPatchFn,
lastScreenshot: ScreenshotResult | undefined,
xPercent: number,
yPercent: number,
takeFreshScreenshot: () => Promise<ScreenshotResult | null>,
logger: Logger,
gridSize: number = DEFAULT_GRID_SIZE,
): Promise<PixelCompareResult> {
if (!lastScreenshot) {
return { valid: true, skipped: true };
}
try {
const fresh = await takeFreshScreenshot();
if (!fresh) {
return { valid: true, skipped: true };
}
const pixelsMatch = comparePixelAtLocation(
crop,
lastScreenshot,
fresh,
xPercent,
yPercent,
gridSize,
);
if (pixelsMatch) {
return { valid: true, skipped: false };
}
return {
valid: false,
skipped: false,
warning:
"Screen content at the target location changed since the last screenshot. Take a new screenshot before clicking.",
};
} catch (err) {
// Skip validation on technical errors, execute action anyway.
// Battle-tested: validation failure must never block the click.
logger.debug("[pixelCompare] validation error, skipping", err);
return { valid: true, skipped: true };
}
}

View File

@@ -1,43 +0,0 @@
/**
* Bundle IDs that are escalations-in-disguise. The approval UI shows a warning
* badge for these; they are NOT blocked. Power users may legitimately want the
* model controlling a terminal.
*
* Imported by the renderer via the `./sentinelApps` subpath (package.json
* `exports`), which keeps Next.js from reaching index.ts → mcpServer.ts →
* @modelcontextprotocol/sdk (devDep, would fail module resolution). Keep
* this file import-free so the subpath stays clean.
*/
/** These apps can execute arbitrary shell commands. */
const SHELL_ACCESS_BUNDLE_IDS = new Set([
"com.apple.Terminal",
"com.googlecode.iterm2",
"com.microsoft.VSCode",
"dev.warp.Warp-Stable",
"com.github.wez.wezterm",
"io.alacritty",
"net.kovidgoyal.kitty",
"com.jetbrains.intellij",
"com.jetbrains.pycharm",
]);
/** Finder in the allowlist ≈ browse + open-any-file. */
const FILESYSTEM_ACCESS_BUNDLE_IDS = new Set(["com.apple.finder"]);
const SYSTEM_SETTINGS_BUNDLE_IDS = new Set(["com.apple.systempreferences"]);
export const SENTINEL_BUNDLE_IDS: ReadonlySet<string> = new Set([
...SHELL_ACCESS_BUNDLE_IDS,
...FILESYSTEM_ACCESS_BUNDLE_IDS,
...SYSTEM_SETTINGS_BUNDLE_IDS,
]);
export type SentinelCategory = "shell" | "filesystem" | "system_settings";
export function getSentinelCategory(bundleId: string): SentinelCategory | null {
if (SHELL_ACCESS_BUNDLE_IDS.has(bundleId)) return "shell";
if (FILESYSTEM_ACCESS_BUNDLE_IDS.has(bundleId)) return "filesystem";
if (SYSTEM_SETTINGS_BUNDLE_IDS.has(bundleId)) return "system_settings";
return null;
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,706 +0,0 @@
/**
* MCP tool schemas for the computer-use server. Mirrors
* claude-for-chrome-mcp/src/browserTools.ts in shape (plain `Tool`-shaped
* object literals, no zod).
*
* Coordinate descriptions are baked in at tool-list build time from the
* `chicago_coordinate_mode` gate. The model sees exactly ONE coordinate
* convention in the param descriptions and never learns the other exists.
* The host (`serverDef.ts`) reads the same frozen gate value for
* `scaleCoord` — both must agree or clicks land in the wrong space.
*/
import type { Tool } from "@modelcontextprotocol/sdk/types.js";
import type { CoordinateMode } from "./types.js";
// See packages/desktop/computer-use-mcp/COORDINATES.md before touching any
// model-facing coordinate text. Chrome's browserTools.ts:143 is the reference
// phrasing — "pixels from the left edge", no geometry, no number to do math with.
const COORD_DESC: Record<CoordinateMode, { x: string; y: string }> = {
pixels: {
x: "Horizontal pixel position read directly from the most recent screenshot image, measured from the left edge. The server handles all scaling.",
y: "Vertical pixel position read directly from the most recent screenshot image, measured from the top edge. The server handles all scaling.",
},
normalized_0_100: {
x: "Horizontal position as a percentage of screen width, 0.0100.0 (0 = left edge, 100 = right edge).",
y: "Vertical position as a percentage of screen height, 0.0100.0 (0 = top edge, 100 = bottom edge).",
},
};
const FRONTMOST_GATE_DESC =
"The frontmost application must be in the session allowlist at the time of this call, or this tool returns an error and does nothing.";
/**
* Item schema for the `actions` array in `computer_batch`, `teach_step`, and
* `teach_batch`. All three dispatch through the same `dispatchAction` path
* with the same validation — keep this enum in sync with `BATCHABLE_ACTIONS`
* in toolCalls.ts.
*/
const BATCH_ACTION_ITEM_SCHEMA = {
type: "object",
properties: {
action: {
type: "string",
enum: [
"key",
"type",
"mouse_move",
"left_click",
"left_click_drag",
"right_click",
"middle_click",
"double_click",
"triple_click",
"scroll",
"hold_key",
"screenshot",
"cursor_position",
"left_mouse_down",
"left_mouse_up",
"wait",
],
description: "The action to perform.",
},
coordinate: {
type: "array",
items: { type: "number" },
minItems: 2,
maxItems: 2,
description:
"(x, y) for click/mouse_move/scroll/left_click_drag end point.",
},
start_coordinate: {
type: "array",
items: { type: "number" },
minItems: 2,
maxItems: 2,
description:
"(x, y) drag start — left_click_drag only. Omit to drag from current cursor.",
},
text: {
type: "string",
description:
"For type: the text. For key/hold_key: the chord string. For click/scroll: modifier keys to hold.",
},
scroll_direction: {
type: "string",
enum: ["up", "down", "left", "right"],
},
scroll_amount: { type: "integer", minimum: 0, maximum: 100 },
duration: {
type: "number",
description: "Seconds (0100). For hold_key/wait.",
},
repeat: {
type: "integer",
minimum: 1,
maximum: 100,
description: "For key: repeat count.",
},
},
required: ["action"],
};
/**
* Build the tool list. Parameterized by capabilities and coordinate mode so
* descriptions are honest and unambiguous (plan §1 — "Unfiltered + honest").
*
* `coordinateMode` MUST match what the host passes to `scaleCoord` at tool-
* -call time. Both should read the same frozen-at-load gate constant.
*
* `installedAppNames` — optional pre-sanitized list of app display names to
* enumerate in the `request_access` description. The caller is responsible
* for sanitization (length cap, character allowlist, sort, count cap) —
* this function just splices the list into the description verbatim. Omit
* to fall back to the generic "display names or bundle IDs" wording.
*/
export function buildComputerUseTools(
caps: {
screenshotFiltering: "native" | "none";
platform: "darwin" | "win32";
/** Include request_teach_access + teach_step. Read once at server construction. */
teachMode?: boolean;
},
coordinateMode: CoordinateMode,
installedAppNames?: string[],
): Tool[] {
const coord = COORD_DESC[coordinateMode];
// Shared hint suffix for BOTH request_access and request_teach_access —
// they use the same resolveRequestedApps path, so the model should get
// the same enumeration for both.
const installedAppsHint =
installedAppNames && installedAppNames.length > 0
? ` Available applications on this machine: ${installedAppNames.join(", ")}.`
: "";
// [x, y]` tuple — param shape for all
// click/move/scroll tools.
const coordinateTuple = {
type: "array",
items: { type: "number" },
minItems: 2,
maxItems: 2,
description: `(x, y): ${coord.x}`,
};
// Modifier hold during click. Shared across all 5 click variants.
const clickModifierText = {
type: "string",
description:
'Modifier keys to hold during the click (e.g. "shift", "ctrl+shift"). Supports the same syntax as the key tool.',
};
const screenshotDesc =
caps.screenshotFiltering === "native"
? "Take a screenshot of the primary display. Applications not in the session allowlist are excluded at the compositor level — only granted apps and the desktop are visible."
: "Take a screenshot of the primary display. On this platform, screenshots are NOT filtered — all open windows are visible. Input actions targeting apps not in the session allowlist are rejected.";
return [
{
name: "request_access",
description:
"Request user permission to control a set of applications for this session. Must be called before any other tool in this server. " +
"The user sees a single dialog listing all requested apps and either allows the whole set or denies it. " +
"Call this again mid-session to add more apps; previously granted apps remain granted. " +
"Returns the granted apps, denied apps, and screenshot filtering capability.",
inputSchema: {
type: "object" as const,
properties: {
apps: {
type: "array",
items: { type: "string" },
description:
"Application display names (e.g. \"Slack\", \"Calendar\") or bundle identifiers (e.g. \"com.tinyspeck.slackmacgap\"). Display names are resolved case-insensitively against installed apps." +
installedAppsHint,
},
reason: {
type: "string",
description:
"One-sentence explanation shown to the user in the approval dialog. Explain the task, not the mechanism.",
},
clipboardRead: {
type: "boolean",
description:
"Also request permission to read the user's clipboard (separate checkbox in the dialog).",
},
clipboardWrite: {
type: "boolean",
description:
"Also request permission to write the user's clipboard. When granted, multi-line `type` calls use the clipboard fast path.",
},
systemKeyCombos: {
type: "boolean",
description:
"Also request permission to send system-level key combos (quit app, switch app, lock screen). Without this, those specific combos are blocked.",
},
},
required: ["apps", "reason"],
},
},
{
name: "screenshot",
description:
screenshotDesc +
" Returns an error if the allowlist is empty. The returned image is what subsequent click coordinates are relative to.",
inputSchema: {
type: "object" as const,
properties: {
save_to_disk: {
type: "boolean",
description:
"Save the image to disk so it can be attached to a message for the user. Returns the saved path in the tool result. Only set this when you intend to share the image — screenshots you're just looking at don't need saving.",
},
},
required: [],
},
},
{
name: "zoom",
description:
"Take a higher-resolution screenshot of a specific region of the last full-screen screenshot. Use this liberally to inspect small text, button labels, or fine UI details that are hard to read in the downsampled full-screen image. " +
"IMPORTANT: Coordinates in subsequent click calls always refer to the full-screen screenshot, never the zoomed image. This tool is read-only for inspecting detail.",
inputSchema: {
type: "object" as const,
properties: {
region: {
type: "array",
items: { type: "integer" },
minItems: 4,
maxItems: 4,
description:
"(x0, y0, x1, y1): Rectangle to zoom into, in the coordinate space of the most recent full-screen screenshot. x0,y0 = top-left, x1,y1 = bottom-right.",
},
save_to_disk: {
type: "boolean",
description:
"Save the image to disk so it can be attached to a message for the user. Returns the saved path in the tool result. Only set this when you intend to share the image.",
},
},
required: ["region"],
},
},
{
name: "left_click",
description: `Left-click at the given coordinates. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
text: clickModifierText,
},
required: ["coordinate"],
},
},
{
name: "double_click",
description: `Double-click at the given coordinates. Selects a word in most text editors. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
text: clickModifierText,
},
required: ["coordinate"],
},
},
{
name: "triple_click",
description: `Triple-click at the given coordinates. Selects a line in most text editors. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
text: clickModifierText,
},
required: ["coordinate"],
},
},
{
name: "right_click",
description: `Right-click at the given coordinates. Opens a context menu in most applications. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
text: clickModifierText,
},
required: ["coordinate"],
},
},
{
name: "middle_click",
description: `Middle-click (scroll-wheel click) at the given coordinates. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
text: clickModifierText,
},
required: ["coordinate"],
},
},
{
name: "type",
description: `Type text into whatever currently has keyboard focus. ${FRONTMOST_GATE_DESC} Newlines are supported. For keyboard shortcuts use \`key\` instead.`,
inputSchema: {
type: "object" as const,
properties: {
text: { type: "string", description: "Text to type." },
},
required: ["text"],
},
},
{
name: "key",
description:
`Press a key or key combination (e.g. "return", "escape", "cmd+a", "ctrl+shift+tab"). ${FRONTMOST_GATE_DESC} ` +
"System-level combos (quit app, switch app, lock screen) require the `systemKeyCombos` grant — without it they return an error. All other combos work.",
inputSchema: {
type: "object" as const,
properties: {
text: {
type: "string",
description: 'Modifiers joined with "+", e.g. "cmd+shift+a".',
},
repeat: {
type: "integer",
minimum: 1,
maximum: 100,
description: "Number of times to repeat the key press. Default is 1.",
},
},
required: ["text"],
},
},
{
name: "scroll",
description: `Scroll at the given coordinates. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
scroll_direction: {
type: "string",
enum: ["up", "down", "left", "right"],
description: "Direction to scroll.",
},
scroll_amount: {
type: "integer",
minimum: 0,
maximum: 100,
description: "Number of scroll ticks.",
},
},
required: ["coordinate", "scroll_direction", "scroll_amount"],
},
},
{
name: "left_click_drag",
description: `Press, move to target, and release. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: {
...coordinateTuple,
description: `(x, y) end point: ${coord.x}`,
},
start_coordinate: {
...coordinateTuple,
description: `(x, y) start point. If omitted, drags from the current cursor position. ${coord.x}`,
},
},
required: ["coordinate"],
},
},
{
name: "mouse_move",
description: `Move the mouse cursor without clicking. Useful for triggering hover states. ${FRONTMOST_GATE_DESC}`,
inputSchema: {
type: "object" as const,
properties: {
coordinate: coordinateTuple,
},
required: ["coordinate"],
},
},
{
name: "open_application",
description:
"Bring an application to the front, launching it if necessary. The target application must already be in the session allowlist — call request_access first.",
inputSchema: {
type: "object" as const,
properties: {
app: {
type: "string",
description:
"Display name (e.g. \"Slack\") or bundle identifier (e.g. \"com.tinyspeck.slackmacgap\").",
},
},
required: ["app"],
},
},
{
name: "switch_display",
description:
"Switch which monitor subsequent screenshots capture. Use this when the " +
"application you need is on a different monitor than the one shown. " +
"The screenshot tool tells you which monitor it captured and lists " +
"other attached monitors by name — pass one of those names here. " +
"After switching, call screenshot to see the new monitor. " +
'Pass "auto" to return to automatic monitor selection.',
inputSchema: {
type: "object" as const,
properties: {
display: {
type: "string",
description:
'Monitor name from the screenshot note (e.g. "Built-in Retina Display", ' +
'"LG UltraFine"), or "auto" to re-enable automatic selection.',
},
},
required: ["display"],
},
},
{
name: "list_granted_applications",
description:
"List the applications currently in the session allowlist, plus the active grant flags and coordinate mode. No side effects.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "read_clipboard",
description:
"Read the current clipboard contents as text. Requires the `clipboardRead` grant.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "write_clipboard",
description:
"Write text to the clipboard. Requires the `clipboardWrite` grant.",
inputSchema: {
type: "object" as const,
properties: {
text: { type: "string" },
},
required: ["text"],
},
},
{
name: "wait",
description: "Wait for a specified duration.",
inputSchema: {
type: "object" as const,
properties: {
duration: {
type: "number",
description: "Duration in seconds (0100).",
},
},
required: ["duration"],
},
},
{
name: "cursor_position",
description:
"Get the current mouse cursor position. Returns image-pixel coordinates relative to the most recent screenshot, or logical points if no screenshot has been taken.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "hold_key",
description:
`Press and hold a key or key combination for the specified duration, then release. ${FRONTMOST_GATE_DESC} ` +
"System-level combos require the `systemKeyCombos` grant.",
inputSchema: {
type: "object" as const,
properties: {
text: {
type: "string",
description: 'Key or chord to hold, e.g. "space", "shift+down".',
},
duration: {
type: "number",
description: "Duration in seconds (0100).",
},
},
required: ["text", "duration"],
},
},
{
name: "left_mouse_down",
description:
`Press the left mouse button at the current cursor position and leave it held. ${FRONTMOST_GATE_DESC} ` +
"Use mouse_move first to position the cursor. Call left_mouse_up to release. Errors if the button is already held.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "left_mouse_up",
description:
`Release the left mouse button at the current cursor position. ${FRONTMOST_GATE_DESC} ` +
"Pairs with left_mouse_down. Safe to call even if the button is not currently held.",
inputSchema: {
type: "object" as const,
properties: {},
required: [],
},
},
{
name: "computer_batch",
description:
"Execute a sequence of actions in ONE tool call. Each individual tool call requires a model→API round trip (seconds); " +
"batching a predictable sequence eliminates all but one. Use this whenever you can predict the outcome of several actions ahead — " +
"e.g. click a field, type into it, press Return. Actions execute sequentially and stop on the first error. " +
`${FRONTMOST_GATE_DESC} The frontmost check runs before EACH action inside the batch — if an action opens a non-allowed app, the next action's gate fires and the batch stops there. ` +
"Mid-batch screenshot actions are allowed for inspection but coordinates in subsequent clicks always refer to the PRE-BATCH full-screen screenshot.",
inputSchema: {
type: "object" as const,
properties: {
actions: {
type: "array",
minItems: 1,
items: BATCH_ACTION_ITEM_SCHEMA,
description:
'List of actions. Example: [{"action":"left_click","coordinate":[100,200]},{"action":"type","text":"hello"},{"action":"key","text":"Return"}]',
},
},
required: ["actions"],
},
},
...(caps.teachMode ? buildTeachTools(coord, installedAppsHint) : []),
];
}
/**
* Teach-mode tools. Split out so the spread above stays a single expression;
* takes `coord` so `teach_step.anchor`'s description uses the same
* frozen coordinate-mode phrasing as click coords, and `installedAppsHint`
* so `request_teach_access.apps` gets the same enumeration as
* `request_access.apps` (same resolution path → same hint).
*/
function buildTeachTools(
coord: { x: string; y: string },
installedAppsHint: string,
): Tool[] {
// Shared between teach_step (top-level) and teach_batch (inside steps[]
// items). Depends on coord, so it lives inside this factory.
const teachStepProperties = {
explanation: {
type: "string",
description:
"Tooltip body text. Explain what the user is looking at and why it matters. " +
"This is the ONLY place the user sees your words — be complete but concise.",
},
next_preview: {
type: "string",
description:
"One line describing exactly what will happen when the user clicks Next. " +
'Example: "Next: I\'ll click Create Bucket and type the name." ' +
"Shown below the explanation in a smaller font.",
},
anchor: {
type: "array",
items: { type: "number" },
minItems: 2,
maxItems: 2,
description:
`(x, y) — where the tooltip arrow points. ${coord.x} ` +
"Omit to center the tooltip with no arrow (for general-context steps).",
},
actions: {
type: "array",
// Empty allowed — "read this, click Next" steps.
items: BATCH_ACTION_ITEM_SCHEMA,
description:
"Actions to execute when the user clicks Next. Same item schema as computer_batch.actions. " +
"Empty array is valid for purely explanatory steps. Actions run sequentially and stop on first error.",
},
} as const;
return [
{
name: "request_teach_access",
description:
"Request permission to guide the user through a task step-by-step with on-screen tooltips. " +
"Use this INSTEAD OF request_access when the user wants to LEARN how to do something " +
'(phrases like "teach me", "walk me through", "show me how", "help me learn"). ' +
"On approval the main Claude window hides and a fullscreen tooltip overlay appears. " +
"You then call teach_step repeatedly; each call shows one tooltip and waits for the user to click Next. " +
"Same app-allowlist semantics as request_access, but no clipboard/system-key flags. " +
"Teach mode ends automatically when your turn ends.",
inputSchema: {
type: "object" as const,
properties: {
apps: {
type: "array",
items: { type: "string" },
description:
'Application display names (e.g. "Slack", "Calendar") or bundle identifiers. Resolved case-insensitively against installed apps.' +
installedAppsHint,
},
reason: {
type: "string",
description:
'What you will be teaching. Shown in the approval dialog as "Claude wants to guide you through {reason}". Keep it short and task-focused.',
},
},
required: ["apps", "reason"],
},
},
{
name: "teach_step",
description:
"Show one guided-tour tooltip and wait for the user to click Next. On Next, execute the actions, " +
"take a fresh screenshot, and return both — you do NOT need a separate screenshot call between steps. " +
"The returned image shows the state after your actions ran; anchor the next teach_step against it. " +
"IMPORTANT — the user only sees the tooltip during teach mode. Put ALL narration in `explanation`. " +
"Text you emit outside teach_step calls is NOT visible until teach mode ends. " +
"Pack as many actions as possible into each step's `actions` array — the user waits through " +
"the whole round trip between clicks, so one step that fills a form beats five steps that fill one field each. " +
"Returns {exited:true} if the user clicks Exit — do not call teach_step again after that. " +
"Take an initial screenshot before your FIRST teach_step to anchor it.",
inputSchema: {
type: "object" as const,
properties: teachStepProperties,
required: ["explanation", "next_preview", "actions"],
},
},
{
name: "teach_batch",
description:
"Queue multiple teach steps in one tool call. Parallels computer_batch: " +
"N steps → one model↔API round trip instead of N. Each step still shows a tooltip " +
"and waits for the user's Next click, but YOU aren't waiting for a round trip between steps. " +
"You can call teach_batch multiple times in one tour — treat each batch as one predictable " +
"SEGMENT (typically: all the steps on one page). The returned screenshot shows the state " +
"after the batch's final actions; anchor the NEXT teach_batch against it. " +
"WITHIN a batch, all anchors and click coordinates refer to the PRE-BATCH screenshot " +
"(same invariant as computer_batch) — for steps 2+ in a batch, either omit anchor " +
"(centered tooltip) or target elements you know won't have moved. " +
"Good pattern: batch 5 tooltips on page A (last step navigates) → read returned screenshot → " +
"batch 3 tooltips on page B → done. " +
"Returns {exited:true, stepsCompleted:N} if the user clicks Exit — do NOT call again after that; " +
"{stepsCompleted, stepFailed, ...} if an action errors mid-batch; " +
"otherwise {stepsCompleted, results:[...]} plus a final screenshot. " +
"Fall back to individual teach_step calls when you need to react to each intermediate screenshot.",
inputSchema: {
type: "object" as const,
properties: {
steps: {
type: "array",
minItems: 1,
items: {
type: "object",
properties: teachStepProperties,
required: ["explanation", "next_preview", "actions"],
},
description:
"Ordered steps. Validated upfront — a typo in step 5 errors before any tooltip shows.",
},
},
required: ["steps"],
},
},
];
}

View File

@@ -1,622 +0,0 @@
import type {
ComputerExecutor,
InstalledApp,
ScreenshotResult,
} from "./executor.js";
/** `ScreenshotResult` without the base64 blob. The shape hosts persist for
* cross-respawn `scaleCoord` survival. */
export type ScreenshotDims = Omit<ScreenshotResult, "base64">;
/** Shape mirrors claude-for-chrome-mcp/src/types.ts:1-7 */
export interface Logger {
info: (message: string, ...args: unknown[]) => void;
error: (message: string, ...args: unknown[]) => void;
warn: (message: string, ...args: unknown[]) => void;
debug: (message: string, ...args: unknown[]) => void;
silly: (message: string, ...args: unknown[]) => void;
}
/**
* Per-app permission tier. Hardcoded by category at grant time — the
* approval dialog displays the tier but the user cannot change it (for now).
*
* - `"read"` — visible in screenshots, NO interaction (no clicks, no typing).
* Browsers land here: the model can read a page that's already open, but
* must use the Claude-in-Chrome MCP for any navigation/clicking. Trading
* platforms land here too (no CiC alternative — the model asks the user).
* - `"click"` — visible + plain left-click, scroll. NO typing/keys,
* NO right/middle-click, NO modifier-clicks, NO drag-drop (all text-
* injection vectors). Terminals/IDEs land here: the model can click a
* Run button or scroll test output, but `type("rm -rf /")` is blocked
* and so is right-click→Paste and dragging text onto the terminal.
* - `"full"` — visible + click + type/key/paste. Everything else.
*
* Enforced in `runInputActionGates` via the frontmost-app check: keyboard
* actions require `"full"`, mouse actions require `"click"` or higher.
*/
export type CuAppPermTier = "read" | "click" | "full";
/**
* A single app the user has approved for the current session. Session-scoped
* only — there is no "once" or "forever" scope (unlike Chrome's per-domain
* three-way). CU has no natural "once" unit; one task = hundreds of clicks.
* Mirrors how `chromeAllowedDomains` is a plain `string[]` with no per-item
* scope.
*/
export interface AppGrant {
bundleId: string;
displayName: string;
/** Epoch ms. For Settings-page display ("Granted 3m ago"). */
grantedAt: number;
/** Undefined → `"full"` (back-compat for pre-tier grants persisted in
* session state). */
tier?: CuAppPermTier;
}
/** Orthogonal to the app allowlist. */
export interface CuGrantFlags {
clipboardRead: boolean;
clipboardWrite: boolean;
/**
* When false, the `key` tool rejects combos in `keyBlocklist.ts`
* (cmd+q, cmd+tab, cmd+space, cmd+shift+q, ctrl+alt+delete). All other
* key sequences work regardless.
*/
systemKeyCombos: boolean;
}
export const DEFAULT_GRANT_FLAGS: CuGrantFlags = {
clipboardRead: false,
clipboardWrite: false,
systemKeyCombos: false,
};
/**
* Host picks via GrowthBook JSON feature `chicago_coordinate_mode`, baked
* into tool param descriptions at server-construction time. The model sees
* ONE convention and never learns the other exists. `normalized_0_100`
* sidesteps the Retina scaleFactor bug class entirely.
*/
export type CoordinateMode = "pixels" | "normalized_0_100";
/**
* Independent kill switches for subtle/risky ported behaviors. Read from
* GrowthBook by the host adapter, consulted in `toolCalls.ts`.
*/
export interface CuSubGates {
/** 9×9 exact-byte staleness guard before click. */
pixelValidation: boolean;
/** Route `type("foo\nbar")` through clipboard instead of keystroke-by-keystroke. */
clipboardPasteMultiline: boolean;
/**
* Ease-out-cubic mouse glide at 60fps, distance-proportional duration
* (2000 px/sec, capped at 0.5s). Adds up to ~0.5s latency
* per click. When off, cursor teleports instantly.
*/
mouseAnimation: boolean;
/**
* Pre-action sequence: hide non-allowlisted apps, then defocus us (from the
* Vercept acquisition). When off, the
* frontmost gate fires in the normal case and the model gets stuck — this
* is the A/B-test-the-old-broken-behavior switch.
*/
hideBeforeAction: boolean;
/**
* Auto-resolve the target display before each screenshot when the
* selected display has no allowed-app windows. When on, `handleScreenshot`
* uses the atomic Swift path; off → sticks with `selectedDisplayId`.
*/
autoTargetDisplay: boolean;
/**
* Stash+clear the clipboard while a tier-"click" app is frontmost.
* Closes the gap where a click-tier terminal/IDE has a UI Paste button
* that's plain-left-clickable — without this, the tier "click"
* keyboard block can be routed around by clicking Paste. Restored when
* a non-"click" app becomes frontmost, or at turn end.
*/
clipboardGuard: boolean;
}
// ----------------------------------------------------------------------------
// Permission request/response (mirror of BridgePermissionRequest, types.ts:77-94)
// ----------------------------------------------------------------------------
/** One entry per app the model asked for, after name → bundle ID resolution. */
export interface ResolvedAppRequest {
/** What the model asked for (e.g. "Slack", "com.tinyspeck.slackmacgap"). */
requestedName: string;
/** The resolved InstalledApp if found, else undefined (shown greyed in the UI). */
resolved?: InstalledApp;
/** Shell-access-equivalent bundle IDs get a UI warning. See sentinelApps.ts. */
isSentinel: boolean;
/** Already in the allowlist → skip the checkbox, return in `granted` immediately. */
alreadyGranted: boolean;
/** Hardcoded tier for this app (browser→"read", terminal→"click", else "full").
* The dialog displays this read-only; the renderer passes it through
* verbatim in the AppGrant. */
proposedTier: CuAppPermTier;
}
/**
* Payload for the renderer approval dialog. Rides through the existing
* `ToolPermissionRequest.input: unknown` field
* (packages/utils/desktop/bridge/common/claude.web.ts:1262) — no IPC schema
* change needed.
*/
export interface CuPermissionRequest {
requestId: string;
/** Model-provided reason string. Shown prominently in the approval UI. */
reason: string;
apps: ResolvedAppRequest[];
/** What the model asked for. User can toggle independently of apps. */
requestedFlags: Partial<CuGrantFlags>;
/**
* For the "On Windows, Claude can see all apps..." footnote. Taken from
* `executor.capabilities.screenshotFiltering` so the renderer doesn't
* need to know about platforms.
*/
screenshotFiltering: "native" | "none";
/**
* Present only when TCC permissions are NOT yet granted. When present,
* the renderer shows a TCC toggle panel (two rows: Accessibility, Screen
* Recording) INSTEAD OF the app list. Clicking a row's "Request" button
* triggers the OS prompt; the store polls on window-focus and flips the
* toggle when the grant is detected. macOS itself prompts the user to
* restart after granting Screen Recording — we don't.
*/
tccState?: {
accessibility: boolean;
screenRecording: boolean;
};
/**
* Apps with windows on the CU display that aren't in the requested
* allowlist. These will be hidden the first time Claude takes an action.
* Computed at request_access time — may be slightly stale by the time the
* user clicks Allow, but it's a preview, not a contract. Absent when
* empty so the renderer can skip the section cleanly.
*/
willHide?: Array<{ bundleId: string; displayName: string }>;
/**
* `chicagoAutoUnhide` app preference at request time. The renderer picks
* between "...then restored when Claude is done" and "...will be hidden"
* copy. Absent when `willHide` is absent (same condition).
*/
autoUnhideEnabled?: boolean;
}
/**
* What the renderer stuffs into `updatedInput._cuGrants` when the user clicks
* "Allow for this session" (mirror of the `_allowAllSites` sentinel at
* LocalAgentModeSessionManager.ts:2794).
*/
export interface CuPermissionResponse {
granted: AppGrant[];
/** Bundle IDs the user unchecked, or apps that weren't installed. */
denied: Array<{ bundleId: string; reason: "user_denied" | "not_installed" }>;
flags: CuGrantFlags;
/**
* Whether the user clicked Allow in THIS dialog. Only set by the
* teach-mode handler — regular request_access doesn't need it (the
* session manager's `result.behavior` gates the merge there). Needed
* because when all requested apps are already granted (skipDialogGrants
* non-empty, needDialog empty), Allow and Deny produce identical
* `{granted:[], denied:[]}` payloads and the tool handler can't tell
* them apart without this. Undefined → legacy/regular path, do not
* gate on it.
*/
userConsented?: boolean;
}
// ----------------------------------------------------------------------------
// Host adapter (mirror of ClaudeForChromeContext, types.ts:33-62)
// ----------------------------------------------------------------------------
/**
* Process-lifetime singleton dependencies. Everything that does NOT vary per
* tool call. Built once by `apps/desktop/src/main/nest-only/chicago/hostAdapter.ts`.
* No Electron imports in this package — the host injects everything.
*/
export interface ComputerUseHostAdapter {
serverName: string;
logger: Logger;
executor: ComputerExecutor;
/**
* TCC state check — Accessibility + Screen Recording on macOS. Pure check,
* no dialog, no relaunch. When either is missing, `request_access` threads
* the state through to the renderer which shows a toggle panel; all other
* tools return a tool error.
*/
ensureOsPermissions(): Promise<
| { granted: true }
| { granted: false; accessibility: boolean; screenRecording: boolean }
>;
/** The Settings-page kill switch (`chicagoEnabled` app preference). */
isDisabled(): boolean;
/**
* The `chicagoAutoUnhide` app preference. Consumed by `buildAccessRequest`
* to populate `CuPermissionRequest.autoUnhideEnabled` so the renderer's
* "will be hidden" copy can say "then restored" only when true.
*/
getAutoUnhideEnabled(): boolean;
/**
* Sub-gates re-read on every tool call so GrowthBook flips take effect
* mid-session without restart.
*/
getSubGates(): CuSubGates;
/**
* JPEG decode + crop + raw pixel bytes, for the PixelCompare staleness guard.
* Injected so this package stays Electron-free. The host implements it via
* `nativeImage.createFromBuffer(jpeg).crop(rect).toBitmap()` — Chromium's
* decoders, BSD-licensed, no `.node` binary.
*
* Returns null on decode/crop failure — caller treats null as `skipped`,
* click proceeds (validation failure must never block the action).
*/
cropRawPatch(
jpegBase64: string,
rect: { x: number; y: number; width: number; height: number },
): Buffer | null;
}
// ----------------------------------------------------------------------------
// Session context (getter/callback bag for bindSessionContext)
// ----------------------------------------------------------------------------
/**
* Per-session state binding for `bindSessionContext`. Hosts build this once
* per session with getters that read fresh from their session store and
* callbacks that write back. The returned dispatcher builds
* `ComputerUseOverrides` from these getters on every call.
*
* Callbacks must be set at construction time — `bindSessionContext` reads
* them once at bind, not per call.
*
* The lock hooks are **async** — `bindSessionContext` awaits them before
* `handleToolCall`, then passes `checkCuLock: undefined` in overrides so the
* sync Gate-3 in `handleToolCall` no-ops. Hosts with in-memory sync locks
* (Cowork) wrap them trivially; hosts with cross-process locks (the CLI's
* O_EXCL file) call the real async primitive directly.
*/
export interface ComputerUseSessionContext {
// ── Read state fresh per call ──────────────────────────────────────
getAllowedApps(): readonly AppGrant[];
getGrantFlags(): CuGrantFlags;
/** Per-user auto-deny list (Settings page). Empty array = none. */
getUserDeniedBundleIds(): readonly string[];
getSelectedDisplayId(): number | undefined;
getDisplayPinnedByModel?(): boolean;
getDisplayResolvedForApps?(): string | undefined;
getTeachModeActive?(): boolean;
/** Dims-only fallback when `lastScreenshot` is unset (cross-respawn).
* `bindSessionContext` reconstructs `{...dims, base64: ""}` so scaleCoord
* works and pixelCompare correctly skips. */
getLastScreenshotDims?(): ScreenshotDims | undefined;
// ── Write-back callbacks ───────────────────────────────────────────
/** Shows the approval dialog. Host routes to its UI, awaits user. The
* signal is aborted if the tool call finishes before the user answers
* (MCP timeout, etc.) — hosts dismiss the dialog on abort. */
onPermissionRequest?(
req: CuPermissionRequest,
signal: AbortSignal,
): Promise<CuPermissionResponse>;
/** Teach-mode sibling of `onPermissionRequest`. */
onTeachPermissionRequest?(
req: CuTeachPermissionRequest,
signal: AbortSignal,
): Promise<CuPermissionResponse>;
/** Called by `bindSessionContext` after merging a permission response into
* the allowlist (dedupe on bundleId, truthy-only flag spread). Host
* persists for resume survival. */
onAllowedAppsChanged?(apps: readonly AppGrant[], flags: CuGrantFlags): void;
onAppsHidden?(bundleIds: string[]): void;
/** Reads the session's clipboardGuard stash. undefined → no stash held. */
getClipboardStash?(): string | undefined;
/** Writes the clipboardGuard stash. undefined clears it. */
onClipboardStashChanged?(stash: string | undefined): void;
onResolvedDisplayUpdated?(displayId: number): void;
onDisplayPinned?(displayId: number | undefined): void;
onDisplayResolvedForApps?(sortedBundleIdsKey: string): void;
/** Called after each screenshot. Host persists for respawn survival. */
onScreenshotCaptured?(dims: ScreenshotDims): void;
onTeachModeActivated?(): void;
onTeachStep?(req: TeachStepRequest): Promise<TeachStepResult>;
onTeachWorking?(): void;
// ── Lock (async) ───────────────────────────────────────────────────
/** At most one session uses CU at a time. Awaited by `bindSessionContext`
* before dispatch. Undefined → no lock gating (proceed). */
checkCuLock?(): Promise<{ holder: string | undefined; isSelf: boolean }>;
/** Take the lock. Called when `checkCuLock` returned `holder: undefined`
* on a non-deferring tool. Host emits enter-CU signals here. */
acquireCuLock?(): Promise<void>;
/** Host-specific lock-held error text. Default is the package's generic
* message. The CLI host includes the holder session-ID prefix. */
formatLockHeldMessage?(holder: string): string;
/** User-abort signal. Passed through to `ComputerUseOverrides.isAborted`
* for the mid-loop checks in handleComputerBatch / handleType. See that
* field for semantics. */
isAborted?(): boolean;
}
// ----------------------------------------------------------------------------
// Per-call overrides (mirror of PermissionOverrides, types.ts:97-102)
// ----------------------------------------------------------------------------
/**
* Built FRESH on every tool call by `bindSessionContext` from
* `ComputerUseSessionContext` getters. This is what lets a singleton MCP
* server carry per-session state — the state lives on the host's session
* store, not the server.
*/
export interface ComputerUseOverrides {
allowedApps: AppGrant[];
grantFlags: CuGrantFlags;
coordinateMode: CoordinateMode;
/**
* User-configured auto-deny list (Settings → Desktop app → Computer Use).
* Bundle IDs
* here are stripped from request_access BEFORE the approval dialog — they
* never reach the user for approval regardless of tier. The response tells
* the agent to ask the user to remove the app from their deny list in
* Settings if access is genuinely needed.
*
* Per-USER, persists across restarts (read from appPreferences per call,
* not session state). Contrast with `allowedApps` which is per-session.
* Empty array = no user-configured denies (the default).
*/
userDeniedBundleIds: readonly string[];
/**
* Display CU operates on; read fresh per call. `scaleCoord` uses the
* `originX/Y` snapshotted in `lastScreenshot`, so mid-session switches
* only affect the NEXT screenshot/prepare call.
*/
selectedDisplayId?: number;
/**
* The `request_access` tool handler calls this and awaits. The wrapper
* closure in serverDef.ts (mirroring InternalMcpServerManager.ts:131-177)
* routes through `handleToolPermission` → IPC → renderer ChicagoApproval.
* When it resolves, the wrapper side-effectfully mutates
* `InternalServerContext.cuAllowedApps` BEFORE returning here.
*
* Undefined when the session wasn't wired with a permission handler (e.g.
* a future headless mode). `request_access` returns a tool error in that case.
*/
onPermissionRequest?: (req: CuPermissionRequest) => Promise<CuPermissionResponse>;
/**
* For the pixel-validation staleness guard. The model's-last-screenshot,
* stashed by serverDef.ts after each `screenshot` tool call. Undefined on
* cold start → pixel validation skipped (click proceeds).
*/
lastScreenshot?: ScreenshotResult;
/**
* Fired after every `prepareForAction` with the bundle IDs it just hid.
* The wrapper closure in serverDef.ts accumulates these into
* `Session.cuHiddenDuringTurn` via a write-through callback (same pattern
* as `onCuPermissionUpdated`). At turn end (`sdkMessage.type === "result"`),
* if the `chicagoAutoUnhide` setting is on, everything in the set is
* unhidden. Set is cleared regardless of the setting so it doesn't leak
* across turns.
*
* Undefined when the session wasn't wired with a tracker — unhide just
* doesn't happen.
*/
onAppsHidden?: (bundleIds: string[]) => void;
/**
* Reads the clipboardGuard stash from session state. `undefined` means no
* stash is held — `syncClipboardStash` stashes on first entry to click-tier
* and clears on restore. Sibling of the `cuHiddenDuringTurn` getter pattern
* — state lives on the host's session, not module-level here.
*/
getClipboardStash?: () => string | undefined;
/**
* Writes the clipboardGuard stash to session state. `undefined` clears.
* Sibling of `onAppsHidden` — the wrapper closure writes through to
* `Session.cuClipboardStash`. At turn end the host reads + clears it
* directly and restores via Electron's `clipboard.writeText` (no nest-only
* import surface).
*/
onClipboardStashChanged?: (stash: string | undefined) => void;
/**
* Write the resolver's picked display back to session so teach overlay
* positioning and subsequent non-resolver calls use the same display.
* Fired by `handleScreenshot` in the atomic `autoTargetDisplay` path when
* `resolvePrepareCapture`'s pick differs from `selectedDisplayId`.
* Fire-and-forget.
*/
onResolvedDisplayUpdated?: (displayId: number) => void;
/**
* Set when the model explicitly picked a display via `switch_display`.
* When true, `handleScreenshot` passes `autoResolve: false` so the Swift
* resolver honors `selectedDisplayId` directly (straight cuDisplayInfo
* passthrough) instead of running the co-location/chase chain. The
* resolver's Step 2 ("host + allowed co-located → host") otherwise
* overrides any `selectedDisplayId` whenever an allowed app shares the
* host's monitor.
*/
displayPinnedByModel?: boolean;
/**
* Write the model's explicit display pick to session. `displayId:
* undefined` clears both `selectedDisplayId` and the pin (back to auto).
* Sibling of `onResolvedDisplayUpdated` but also sets the pin flag —
* the two are semantically distinct (resolver-picked vs model-picked).
*/
onDisplayPinned?: (displayId: number | undefined) => void;
/**
* Sorted comma-joined bundle-ID set the display was last auto-resolved
* for. `handleScreenshot` compares this to the current allowed set and
* only passes `autoResolve: true` when they differ — so the resolver
* doesn't yank the display on every screenshot, only when the app set
* has changed since the last resolve (or manual switch).
*/
displayResolvedForApps?: string;
/**
* Records which app set the current display selection was made for. Fired
* alongside `onResolvedDisplayUpdated` when the resolver picks, so the next
* screenshot sees a matching set and skips auto-resolve.
*/
onDisplayResolvedForApps?: (sortedBundleIdsKey: string) => void;
/**
* Global CU lock — at most one session actively uses CU at a time. Checked
* in `handleToolCall` after kill-switch/TCC, before dispatch. Every CU tool
* including `request_access` goes through it.
*
* - `holder === undefined` → lock is free, safe to acquire
* - `isSelf === true` → this session already holds it (no-op, proceed)
* - `holder !== undefined && !isSelf` → blocked, return tool error
*
* `undefined` callback → lock system not wired (e.g. CCD). Proceed without
* gating — absence of the mechanism ≠ locked out.
*
* The host manages release (on session idle/stop/archive) — this package
* never releases.
*/
checkCuLock?: () => { holder: string | undefined; isSelf: boolean };
/**
* Take the lock for this session. `handleToolCall` calls this exactly once
* per turn, on the FIRST CU tool call when `checkCuLock().holder` is
* undefined. No-op if already held (defensive — the check should have
* short-circuited). Host emits an event the overlay listens to.
*/
acquireCuLock?: () => void;
/**
* User-abort signal. Checked mid-iteration inside `handleComputerBatch`
* and `handleType`'s grapheme loop so an in-flight batch/type stops
* promptly on overlay Stop instead of running to completion after the
* host has already abandoned the tool result.
*
* Undefined → never aborts (e.g. unwired host). Live per-check read —
* same lazy-getter pattern as `checkCuLock`.
*/
isAborted?: () => boolean;
// ── Teach mode ───────────────────────────────────────────────────────
// Wired only when the host's teachModeEnabled gate is on. All five
// undefined → `request_teach_access` / `teach_step` return tool errors
// and teach mode is effectively off.
/**
* Sibling of `onPermissionRequest`. Same blocking-await-on-renderer-dialog
* semantics, but routes to ComputerUseTeachApproval.tsx (which explains
* the window-hides-during-guide behavior) instead of ComputerUseApproval.
* The wrapper closure in serverDef.ts writes grants through to session state
* via `onCuPermissionUpdated` exactly as `onPermissionRequest` does.
*/
onTeachPermissionRequest?: (
req: CuTeachPermissionRequest,
) => Promise<CuPermissionResponse>;
/**
* Called by `handleRequestTeachAccess` after the user approves and at least
* one app was granted. Host sets `session.teachModeActive = true`, emits
* `teachModeChanged` → teach controller hides the main window and shows the
* fullscreen overlay. Cleared by the host on turn end (`transitionTo("idle")`)
* alongside the CU lock release.
*/
onTeachModeActivated?: () => void;
/**
* Read by `handleRequestAccess` and `handleRequestTeachAccess` to
* short-circuit with a clear tool error when teach mode is active. The
* main window is hidden during teach mode, so permission dialogs render
* invisibly and handleToolPermission blocks forever on an invisible
* prompt. Better to tell the model to exit teach mode first. Getter
* (not a boolean field) because teach mode state lives on the session,
* not on this per-call overrides object.
*/
getTeachModeActive?: () => boolean;
/**
* Called by `handleTeachStep` with the scaled anchor + text. Host stores
* the resolver, emits `teachStepRequested` → teach controller pushes the
* payload to the overlay → user reads, clicks Next → IPC → host calls the
* stored resolver → this promise resolves. `{action: "exit"}` when the user
* clicks Exit (or the turn is interrupted) — `handleTeachStep` short-circuits
* without executing actions.
*
* Same blocking-promise pattern as `onPermissionRequest`, but resolved by
* the teach overlay's own preload (not the main renderer's tool-approval UI).
*/
onTeachStep?: (req: TeachStepRequest) => Promise<TeachStepResult>;
/**
* Called immediately after `onTeachStep` resolves with "next", before
* action dispatch begins. Host emits `teachStepWorking` → overlay flips to
* the spinner state (Next button gone, Exit stays, "Working…" + rotating
* notch). The next `onTeachStep` call replaces the spinner with the new
* tooltip content.
*/
onTeachWorking?: () => void;
}
// ----------------------------------------------------------------------------
// Teach mode (guided-tour tooltips with Next-button action execution)
// ----------------------------------------------------------------------------
/**
* Payload the host pushes to the teach overlay BrowserWindow. Built by
* `handleTeachStep` in toolCalls.ts from the model's `teach_step` args.
*
* `anchorLogical` here is POST-`scaleCoord` — **full-display** logical
* macOS points (origin = monitor top-left, menu bar included, since
* cuDisplayInfo returns CGDisplayBounds). The overlay window is positioned
* at `workArea.{x,y}` (excludes menu bar/Dock), so `updateTeachStep` in
* teach/window.ts subtracts the workArea offset before IPC so the HTML's
* CSS coords match.
*/
export interface TeachStepRequest {
explanation: string;
nextPreview: string;
/** Full-display logical points. Undefined → overlay centers the tooltip, hides the arrow. */
anchorLogical?: { x: number; y: number };
}
export type TeachStepResult = { action: "next" } | { action: "exit" };
/**
* Payload for the renderer's ComputerUseTeachApproval dialog. Rides through
* `ToolPermissionRequest.input: unknown` same as `CuPermissionRequest`.
* Separate type (not a flag on `CuPermissionRequest`) so the two approval
* components can narrow independently and the teach dialog is free to drop
* fields it doesn't render (no grant-flag checkboxes in teach mode).
*/
export interface CuTeachPermissionRequest {
requestId: string;
/** Model-provided reason. Shown in the dialog headline ("guide you through {reason}"). */
reason: string;
apps: ResolvedAppRequest[];
screenshotFiltering: "native" | "none";
/** Present only when TCC is ungranted — same semantics as `CuPermissionRequest.tccState`. */
tccState?: {
accessibility: boolean;
screenRecording: boolean;
};
willHide?: Array<{ bundleId: string; displayName: string }>;
/** Same semantics as `CuPermissionRequest.autoUnhideEnabled`. */
autoUnhideEnabled?: boolean;
}

View File

@@ -1,23 +0,0 @@
const path = require("path");
if (process.platform !== "darwin") {
throw new Error("@ant/computer-use-swift is only available on macOS");
}
// COMPUTER_USE_SWIFT_NODE_PATH: escape hatch for bundlers. Bun's --compile
// embeds the .node as an asset, not in a node_modules tree — __dirname is the
// exe dir and ../prebuilds/ doesn't exist. The consuming build bakes this var
// to the embedded asset's path. Unset → normal node_modules layout.
//
// Four methods use `Task { @MainActor in ... }` (captureExcluding,
// captureRegion, apps.listInstalled, resolvePrepareCapture) which enqueue
// onto DispatchQueue.main. Electron drains that queue via CFRunLoop; libuv
// (Node/bun) does not — the promises hang. Consumers running under libuv
// must pump `_drainMainRunLoop` via setInterval while those promises are
// pending. Consumers under Electron don't need to (CFRunLoop drains
// automatically).
const native = require(
process.env.COMPUTER_USE_SWIFT_NODE_PATH ??
path.resolve(__dirname, "../prebuilds/computer_use.node"),
);
module.exports = native.computerUse;

View File

@@ -1,163 +0,0 @@
// Copied from https://github.com/aws/aws-sdk-js-v3/blob/bee66fbd2a519a16b57c787b2689af857af720af/clients/client-bedrock-runtime/src/protocols/Aws_restJson1.ts
// Modified to remove unnecessary code (we only need to call `de_ResponseStream`) and to adjust imports.
import { collectBody, decorateServiceException as __decorateServiceException, expectInt32 as __expectInt32, expectString as __expectString, map, take, } from '@smithy/smithy-client';
import { InternalServerException, ModelStreamErrorException, ThrottlingException, ValidationException, } from '@aws-sdk/client-bedrock-runtime';
/**
* deserializeAws_restJson1InternalServerExceptionRes
*/
const de_InternalServerExceptionRes = async (parsedOutput, context) => {
const contents = map({});
const data = parsedOutput.body;
const doc = take(data, {
message: __expectString,
});
Object.assign(contents, doc);
const exception = new InternalServerException({
$metadata: deserializeMetadata(parsedOutput),
...contents,
});
return __decorateServiceException(exception, parsedOutput.body);
};
/**
* deserializeAws_restJson1ModelStreamErrorExceptionRes
*/
const de_ModelStreamErrorExceptionRes = async (parsedOutput, context) => {
const contents = map({});
const data = parsedOutput.body;
const doc = take(data, {
message: __expectString,
originalMessage: __expectString,
originalStatusCode: __expectInt32,
});
Object.assign(contents, doc);
const exception = new ModelStreamErrorException({
$metadata: deserializeMetadata(parsedOutput),
...contents,
});
return __decorateServiceException(exception, parsedOutput.body);
};
/**
* deserializeAws_restJson1ThrottlingExceptionRes
*/
const de_ThrottlingExceptionRes = async (parsedOutput, context) => {
const contents = map({});
const data = parsedOutput.body;
const doc = take(data, {
message: __expectString,
});
Object.assign(contents, doc);
const exception = new ThrottlingException({
$metadata: deserializeMetadata(parsedOutput),
...contents,
});
return __decorateServiceException(exception, parsedOutput.body);
};
/**
* deserializeAws_restJson1ValidationExceptionRes
*/
const de_ValidationExceptionRes = async (parsedOutput, context) => {
const contents = map({});
const data = parsedOutput.body;
const doc = take(data, {
message: __expectString,
});
Object.assign(contents, doc);
const exception = new ValidationException({
$metadata: deserializeMetadata(parsedOutput),
...contents,
});
return __decorateServiceException(exception, parsedOutput.body);
};
/**
* deserializeAws_restJson1ResponseStream
*/
export const de_ResponseStream = (output, context) => {
return context.eventStreamMarshaller.deserialize(output, async (event) => {
if (event['chunk'] != null) {
return {
chunk: await de_PayloadPart_event(event['chunk'], context),
};
}
if (event['internalServerException'] != null) {
return {
internalServerException: await de_InternalServerException_event(event['internalServerException'], context),
};
}
if (event['modelStreamErrorException'] != null) {
return {
modelStreamErrorException: await de_ModelStreamErrorException_event(event['modelStreamErrorException'], context),
};
}
if (event['validationException'] != null) {
return {
validationException: await de_ValidationException_event(event['validationException'], context),
};
}
if (event['throttlingException'] != null) {
return {
throttlingException: await de_ThrottlingException_event(event['throttlingException'], context),
};
}
return { $unknown: output };
});
};
const de_InternalServerException_event = async (output, context) => {
const parsedOutput = {
...output,
body: await parseBody(output.body, context),
};
return de_InternalServerExceptionRes(parsedOutput, context);
};
const de_ModelStreamErrorException_event = async (output, context) => {
const parsedOutput = {
...output,
body: await parseBody(output.body, context),
};
return de_ModelStreamErrorExceptionRes(parsedOutput, context);
};
const de_PayloadPart_event = async (output, context) => {
const contents = {};
const data = await parseBody(output.body, context);
Object.assign(contents, de_PayloadPart(data, context));
return contents;
};
const de_ThrottlingException_event = async (output, context) => {
const parsedOutput = {
...output,
body: await parseBody(output.body, context),
};
return de_ThrottlingExceptionRes(parsedOutput, context);
};
const de_ValidationException_event = async (output, context) => {
const parsedOutput = {
...output,
body: await parseBody(output.body, context),
};
return de_ValidationExceptionRes(parsedOutput, context);
};
/**
* deserializeAws_restJson1PayloadPart
*/
const de_PayloadPart = (output, context) => {
return take(output, {
bytes: context.base64Decoder,
});
};
const deserializeMetadata = (output) => ({
httpStatusCode: output.statusCode,
requestId: output.headers['x-amzn-requestid'] ??
output.headers['x-amzn-request-id'] ??
output.headers['x-amz-request-id'] ??
'',
extendedRequestId: output.headers['x-amz-id-2'] ?? '',
cfId: output.headers['x-amz-cf-id'] ?? '',
});
// Encode Uint8Array data into string with utf-8.
const collectBodyString = (streamBody, context) => collectBody(streamBody, context).then((body) => context.utf8Encoder(body));
const parseBody = (streamBody, context) => collectBodyString(streamBody, context).then((encoded) => {
if (encoded.length) {
return JSON.parse(encoded);
}
return {};
});
//# sourceMappingURL=AWS_restJson1.mjs.map

View File

@@ -1,124 +0,0 @@
import { BaseAnthropic } from '@anthropic-ai/sdk/client';
import * as Resources from '@anthropic-ai/sdk/resources/index';
import { getAuthHeaders } from "./core/auth.mjs";
import { Stream } from "./core/streaming.mjs";
import { readEnv } from "./internal/utils/env.mjs";
import { isObj } from "./internal/utils/values.mjs";
import { buildHeaders } from "./internal/headers.mjs";
import { path } from "./internal/utils/path.mjs";
export { BaseAnthropic } from '@anthropic-ai/sdk/client';
const DEFAULT_VERSION = 'bedrock-2023-05-31';
const MODEL_ENDPOINTS = new Set(['/v1/complete', '/v1/messages', '/v1/messages?beta=true']);
/** API Client for interfacing with the Anthropic Bedrock API. */
export class AnthropicBedrock extends BaseAnthropic {
/**
* API Client for interfacing with the Anthropic Bedrock API.
*
* @param {string | null | undefined} [opts.awsSecretKey]
* @param {string | null | undefined} [opts.awsAccessKey]
* @param {string | undefined} [opts.awsRegion=process.env['AWS_REGION'] ?? us-east-1]
* @param {string | null | undefined} [opts.awsSessionToken]
* @param {(() => Promise<AwsCredentialIdentityProvider>) | null} [opts.providerChainResolver] - Custom provider chain resolver for AWS credentials. Useful for non-Node environments.
* @param {string} [opts.baseURL=process.env['ANTHROPIC_BEDROCK_BASE_URL'] ?? https://bedrock-runtime.${this.awsRegion}.amazonaws.com] - Override the default base URL for the API.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
* @param {boolean} [opts.skipAuth=false] - Skip authentication for this request. This is useful if you have an internal proxy that handles authentication for you.
*/
constructor({ awsRegion = readEnv('AWS_REGION') ?? 'us-east-1', baseURL = readEnv('ANTHROPIC_BEDROCK_BASE_URL') ?? `https://bedrock-runtime.${awsRegion}.amazonaws.com`, awsSecretKey = null, awsAccessKey = null, awsSessionToken = null, providerChainResolver = null, ...opts } = {}) {
super({
baseURL,
...opts,
});
this.skipAuth = false;
this.messages = makeMessagesResource(this);
this.completions = new Resources.Completions(this);
this.beta = makeBetaResource(this);
this.awsSecretKey = awsSecretKey;
this.awsAccessKey = awsAccessKey;
this.awsRegion = awsRegion;
this.awsSessionToken = awsSessionToken;
this.skipAuth = opts.skipAuth ?? false;
this.providerChainResolver = providerChainResolver;
}
validateHeaders() {
// auth validation is handled in prepareRequest since it needs to be async
}
async prepareRequest(request, { url, options }) {
if (this.skipAuth) {
return;
}
const regionName = this.awsRegion;
if (!regionName) {
throw new Error('Expected `awsRegion` option to be passed to the client or the `AWS_REGION` environment variable to be present');
}
const headers = await getAuthHeaders(request, {
url,
regionName,
awsAccessKey: this.awsAccessKey,
awsSecretKey: this.awsSecretKey,
awsSessionToken: this.awsSessionToken,
fetchOptions: this.fetchOptions,
providerChainResolver: this.providerChainResolver,
});
request.headers = buildHeaders([headers, request.headers]).values;
}
async buildRequest(options) {
options.__streamClass = Stream;
if (isObj(options.body)) {
// create a shallow copy of the request body so that code that mutates it later
// doesn't mutate the original user-provided object
options.body = { ...options.body };
}
if (isObj(options.body)) {
if (!options.body['anthropic_version']) {
options.body['anthropic_version'] = DEFAULT_VERSION;
}
if (options.headers && !options.body['anthropic_beta']) {
const betas = buildHeaders([options.headers]).values.get('anthropic-beta');
if (betas != null) {
options.body['anthropic_beta'] = betas.split(',');
}
}
}
if (MODEL_ENDPOINTS.has(options.path) && options.method === 'post') {
if (!isObj(options.body)) {
throw new Error('Expected request body to be an object for post /v1/messages');
}
const model = options.body['model'];
options.body['model'] = undefined;
const stream = options.body['stream'];
options.body['stream'] = undefined;
if (stream) {
options.path = path `/model/${model}/invoke-with-response-stream`;
}
else {
options.path = path `/model/${model}/invoke`;
}
}
return super.buildRequest(options);
}
}
function makeMessagesResource(client) {
const resource = new Resources.Messages(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.batches;
// @ts-expect-error we're deleting non-optional properties
delete resource.countTokens;
return resource;
}
function makeBetaResource(client) {
const resource = new Resources.Beta(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.promptCaching;
// @ts-expect-error we're deleting non-optional properties
delete resource.messages.batches;
// @ts-expect-error we're deleting non-optional properties
delete resource.messages.countTokens;
return resource;
}
//# sourceMappingURL=client.mjs.map

View File

@@ -1,82 +0,0 @@
import { Sha256 } from '@aws-crypto/sha256-js';
import { FetchHttpHandler } from '@smithy/fetch-http-handler';
import { HttpRequest } from '@smithy/protocol-http';
import { SignatureV4 } from '@smithy/signature-v4';
import assert from 'assert';
const DEFAULT_PROVIDER_CHAIN_RESOLVER = () => import('@aws-sdk/credential-providers').then(({ fromNodeProviderChain }) => fromNodeProviderChain({
clientConfig: {
requestHandler: new FetchHttpHandler({
requestInit: (httpRequest) => {
return {
...httpRequest,
};
},
}),
},
}))
.catch((error) => {
throw new Error(`Failed to import '@aws-sdk/credential-providers'.` +
`You can provide a custom \`providerChainResolver\` in the client options if your runtime does not have access to '@aws-sdk/credential-providers': ` +
`\`new AnthropicBedrock({ providerChainResolver })\` ` +
`Original error: ${error.message}`);
});
export const getAuthHeaders = async (req, props) => {
assert(req.method, 'Expected request method property to be set');
const providerChain = await (props.providerChainResolver ?
props.providerChainResolver()
: DEFAULT_PROVIDER_CHAIN_RESOLVER());
const credentials = await withTempEnv(() => {
// Temporarily set the appropriate environment variables if we've been
// explicitly given credentials so that the credentials provider can
// resolve them.
//
// Note: the environment provider is only not run first if the `AWS_PROFILE`
// environment variable is set.
// https://github.com/aws/aws-sdk-js-v3/blob/44a18a34b2c93feccdfcd162928d13e6dbdcaf30/packages/credential-provider-node/src/defaultProvider.ts#L49
if (props.awsAccessKey) {
process.env['AWS_ACCESS_KEY_ID'] = props.awsAccessKey;
}
if (props.awsSecretKey) {
process.env['AWS_SECRET_ACCESS_KEY'] = props.awsSecretKey;
}
if (props.awsSessionToken) {
process.env['AWS_SESSION_TOKEN'] = props.awsSessionToken;
}
}, () => providerChain());
const signer = new SignatureV4({
service: 'bedrock',
region: props.regionName,
credentials,
sha256: Sha256,
});
const url = new URL(props.url);
const headers = !req.headers ? {}
: Symbol.iterator in req.headers ?
Object.fromEntries(Array.from(req.headers).map((header) => [...header]))
: { ...req.headers };
// The connection header may be stripped by a proxy somewhere, so the receiver
// of this message may not see this header, so we remove it from the set of headers
// that are signed.
delete headers['connection'];
headers['host'] = url.hostname;
const request = new HttpRequest({
method: req.method.toUpperCase(),
protocol: url.protocol,
path: url.pathname,
headers,
body: req.body,
});
const signed = await signer.sign(request);
return signed.headers;
};
const withTempEnv = async (updateEnv, fn) => {
const previousEnv = { ...process.env };
try {
updateEnv();
return await fn();
}
finally {
process.env = previousEnv;
}
};
//# sourceMappingURL=auth.mjs.map

View File

@@ -1,2 +0,0 @@
export * from '@anthropic-ai/sdk/core/error';
//# sourceMappingURL=error.mjs.map

View File

@@ -1,108 +0,0 @@
import { EventStreamMarshaller } from '@smithy/eventstream-serde-node';
import { fromBase64, toBase64 } from '@smithy/util-base64';
import { streamCollector } from '@smithy/fetch-http-handler';
import { Stream as CoreStream } from '@anthropic-ai/sdk/streaming';
import { AnthropicError } from '@anthropic-ai/sdk/error';
import { APIError } from '@anthropic-ai/sdk';
import { de_ResponseStream } from "../AWS_restJson1.mjs";
import { ReadableStreamToAsyncIterable } from "../internal/shims.mjs";
import { safeJSON } from "../internal/utils/values.mjs";
import { loggerFor } from "../internal/utils/log.mjs";
export const toUtf8 = (input) => new TextDecoder('utf-8').decode(input);
export const fromUtf8 = (input) => new TextEncoder().encode(input);
// `de_ResponseStream` parses a Bedrock response stream and emits events as they are found.
// It requires a "context" argument which has many fields, but for what we're using it for
// it only needs this.
export const getMinimalSerdeContext = () => {
const marshaller = new EventStreamMarshaller({ utf8Encoder: toUtf8, utf8Decoder: fromUtf8 });
return {
base64Decoder: fromBase64,
base64Encoder: toBase64,
utf8Decoder: fromUtf8,
utf8Encoder: toUtf8,
eventStreamMarshaller: marshaller,
streamCollector: streamCollector,
};
};
export class Stream extends CoreStream {
static fromSSEResponse(response, controller, client) {
let consumed = false;
const logger = client ? loggerFor(client) : console;
async function* iterMessages() {
if (!response.body) {
controller.abort();
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}
const responseBodyIter = ReadableStreamToAsyncIterable(response.body);
const eventStream = de_ResponseStream(responseBodyIter, getMinimalSerdeContext());
for await (const event of eventStream) {
if (event.chunk && event.chunk.bytes) {
const s = toUtf8(event.chunk.bytes);
yield { event: 'chunk', data: s, raw: [] };
}
else if (event.internalServerException) {
yield { event: 'error', data: 'InternalServerException', raw: [] };
}
else if (event.modelStreamErrorException) {
yield { event: 'error', data: 'ModelStreamErrorException', raw: [] };
}
else if (event.validationException) {
yield { event: 'error', data: 'ValidationException', raw: [] };
}
else if (event.throttlingException) {
yield { event: 'error', data: 'ThrottlingException', raw: [] };
}
}
}
// Note: this function is copied entirely from the core SDK
async function* iterator() {
if (consumed) {
throw new Error('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const sse of iterMessages()) {
if (sse.event === 'chunk') {
try {
yield JSON.parse(sse.data);
}
catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
if (sse.event === 'error') {
const errText = sse.data;
const errJSON = safeJSON(errText);
const errMessage = errJSON ? undefined : errText;
throw APIError.generate(undefined, errJSON, errMessage, response.headers);
}
}
done = true;
}
catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e))
return;
throw e;
}
finally {
// If the user `break`s, abort the ongoing request.
if (!done)
controller.abort();
}
}
return new Stream(iterator, controller);
}
}
function isAbortError(err) {
return (typeof err === 'object' &&
err !== null &&
// Spec-compliant fetch implementations
(('name' in err && err.name === 'AbortError') ||
// Expo fetch
('message' in err && String(err.message).includes('FetchRequestCanceledException'))));
}
//# sourceMappingURL=streaming.mjs.map

View File

@@ -1,3 +0,0 @@
export * from "./client.mjs";
export { AnthropicBedrock as default } from "./client.mjs";
//# sourceMappingURL=index.mjs.map

View File

@@ -1,74 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from "./utils/values.mjs";
const brand_privateNullableHeaders = Symbol.for('brand.privateNullableHeaders');
function* iterateHeaders(headers) {
if (!headers)
return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter;
if (headers instanceof Headers) {
iter = headers.entries();
}
else if (isReadonlyArray(headers)) {
iter = headers;
}
else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string')
throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined)
continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders) => {
const targetHeaders = new Headers();
const nullHeaders = new Set();
for (const headers of newHeaders) {
const seenHeaders = new Set();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
}
else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers) => {
for (const _ of iterateHeaders(headers))
return false;
return true;
};
//# sourceMappingURL=headers.mjs.map

View File

@@ -1,85 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export function getDefaultFetch() {
if (typeof fetch !== 'undefined') {
return fetch;
}
throw new Error('`fetch` is not defined as a global; Either pass `fetch` to the client, `new Anthropic({ fetch })` or polyfill the global, `globalThis.fetch = fetch`');
}
export function makeReadableStream(...args) {
const ReadableStream = globalThis.ReadableStream;
if (typeof ReadableStream === 'undefined') {
// Note: All of the platforms / runtimes we officially support already define
// `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes.
throw new Error('`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`');
}
return new ReadableStream(...args);
}
export function ReadableStreamFrom(iterable) {
let iter = Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
return makeReadableStream({
start() { },
async pull(controller) {
const { done, value } = await iter.next();
if (done) {
controller.close();
}
else {
controller.enqueue(value);
}
},
async cancel() {
await iter.return?.();
},
});
}
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable(stream) {
if (stream[Symbol.asyncIterator])
return stream;
const reader = stream.getReader();
return {
async next() {
try {
const result = await reader.read();
if (result?.done)
reader.releaseLock(); // release lock when stream becomes closed
return result;
}
catch (e) {
reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
},
async return() {
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
},
};
}
/**
* Cancels a ReadableStream we don't need to consume.
* See https://undici.nodejs.org/#/?id=garbage-collection
*/
export async function CancelReadableStream(stream) {
if (stream === null || typeof stream !== 'object')
return;
if (stream[Symbol.asyncIterator]) {
await stream[Symbol.asyncIterator]().return?.();
return;
}
const reader = stream.getReader();
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
}
//# sourceMappingURL=shims.mjs.map

View File

@@ -1,18 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env) => {
if (typeof globalThis.process !== 'undefined') {
return globalThis.process.env?.[env]?.trim() ?? undefined;
}
if (typeof globalThis.Deno !== 'undefined') {
return globalThis.Deno.env?.get?.(env)?.trim();
}
return undefined;
};
//# sourceMappingURL=env.mjs.map

View File

@@ -1,80 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from "./values.mjs";
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (maybeLevel, sourceName, client) => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
return undefined;
};
function noop() { }
function makeLogFn(fnLevel, logger, logLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
}
else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = /* @__PURE__ */ new WeakMap();
export function loggerFor(client) {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
name,
(name.toLowerCase() === 'x-api-key' ||
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie') ?
'***'
: value,
]));
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};
//# sourceMappingURL=log.mjs.map

View File

@@ -1,74 +0,0 @@
import { AnthropicError } from "../../core/error.mjs";
/**
* Percent-encode everything that isn't safe to have in a path without encoding safe chars.
*
* Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
* > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
export function encodeURIPath(str) {
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
}
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
export const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
// If there are no params, no processing is needed.
if (statics.length === 1)
return statics[0];
let postPath = false;
const invalidSegments = [];
const path = statics.reduce((previousValue, currentValue, index) => {
if (/[?#]/.test(currentValue)) {
postPath = true;
}
const value = params[index];
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
if (index !== params.length &&
(value == null ||
(typeof value === 'object' &&
// handle values from other realms
value.toString ===
Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY) ?? EMPTY)
?.toString))) {
encoded = value + '';
invalidSegments.push({
start: previousValue.length + currentValue.length,
length: encoded.length,
error: `Value of type ${Object.prototype.toString
.call(value)
.slice(8, -1)} is not a valid path parameter`,
});
}
return previousValue + currentValue + (index === params.length ? '' : encoded);
}, '');
const pathOnly = path.split(/[?#]/, 1)[0];
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
let match;
// Find all invalid segments
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
invalidSegments.push({
start: match.index,
length: match[0].length,
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
});
}
invalidSegments.sort((a, b) => a.start - b.start);
if (invalidSegments.length > 0) {
let lastEnd = 0;
const underline = invalidSegments.reduce((acc, segment) => {
const spaces = ' '.repeat(segment.start - lastEnd);
const arrows = '^'.repeat(segment.length);
lastEnd = segment.start + segment.length;
return acc + spaces + arrows;
}, '');
throw new AnthropicError(`Path parameters result in path with invalid segments:\n${invalidSegments
.map((e) => e.error)
.join('\n')}\n${path}\n${underline}`);
}
return path;
};
/**
* URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
*/
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
//# sourceMappingURL=path.mjs.map

View File

@@ -1,94 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../core/error.mjs";
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url) => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val) => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray;
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x) {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj) {
if (!obj)
return true;
for (const _k in obj)
return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj) {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = (value) => {
if (value == null) {
throw new AnthropicError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name, n) => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new AnthropicError(`${name} must be an integer`);
}
if (n < 0) {
throw new AnthropicError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value) => {
if (typeof value === 'number')
return Math.round(value);
if (typeof value === 'string')
return parseInt(value, 10);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value) => {
if (typeof value === 'number')
return value;
if (typeof value === 'string')
return parseFloat(value);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value) => {
if (typeof value === 'boolean')
return value;
if (typeof value === 'string')
return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value) => {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value) => {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value) => {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text) => {
try {
return JSON.parse(text);
}
catch (err) {
return undefined;
}
};
//# sourceMappingURL=values.mjs.map

View File

@@ -1,98 +0,0 @@
import { buildHeaders } from "./internal/headers.mjs";
import * as Errors from "./core/error.mjs";
import { readEnv } from "./internal/utils.mjs";
import { Anthropic } from '@anthropic-ai/sdk/client';
export { BaseAnthropic } from '@anthropic-ai/sdk/client';
import * as Resources from '@anthropic-ai/sdk/resources/index';
/** API Client for interfacing with the Anthropic Foundry API. */
export class AnthropicFoundry extends Anthropic {
/**
* API Client for interfacing with the Anthropic Foundry API.
*
* @param {string | undefined} [opts.resource=process.env['ANTHROPIC_FOUNDRY_RESOURCE'] ?? undefined] - Your Foundry resource name
* @param {string | undefined} [opts.apiKey=process.env['ANTHROPIC_FOUNDRY_API_KEY'] ?? undefined]
* @param {string | null | undefined} [opts.organization=process.env['ANTHROPIC_ORG_ID'] ?? null]
* @param {string} [opts.baseURL=process.env['ANTHROPIC_FOUNDRY_BASE_URL']] - Sets the base URL for the API, e.g. `https://example-resource.azure.anthropic.com/anthropic/`.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {number} [opts.httpAgent] - An HTTP agent used to manage HTTP(s) connections.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {Headers} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {DefaultQuery} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor({ baseURL = readEnv('ANTHROPIC_FOUNDRY_BASE_URL'), apiKey = readEnv('ANTHROPIC_FOUNDRY_API_KEY'), resource = readEnv('ANTHROPIC_FOUNDRY_RESOURCE'), azureADTokenProvider, dangerouslyAllowBrowser, ...opts } = {}) {
if (typeof azureADTokenProvider === 'function') {
dangerouslyAllowBrowser = true;
}
if (!azureADTokenProvider && !apiKey) {
throw new Errors.AnthropicError('Missing credentials. Please pass one of `apiKey` and `azureTokenProvider`, or set the `ANTHROPIC_FOUNDRY_API_KEY` environment variable.');
}
if (azureADTokenProvider && apiKey) {
throw new Errors.AnthropicError('The `apiKey` and `azureADTokenProvider` arguments are mutually exclusive; only one can be passed at a time.');
}
if (!baseURL) {
if (!resource) {
throw new Errors.AnthropicError('Must provide one of the `baseURL` or `resource` arguments, or the `ANTHROPIC_FOUNDRY_RESOURCE` environment variable');
}
baseURL = `https://${resource}.services.ai.azure.com/anthropic/`;
}
else {
if (resource) {
throw new Errors.AnthropicError('baseURL and resource are mutually exclusive');
}
}
super({
apiKey: azureADTokenProvider ?? apiKey,
baseURL,
...opts,
...(dangerouslyAllowBrowser !== undefined ? { dangerouslyAllowBrowser } : {}),
});
this.resource = null;
// @ts-expect-error are using a different Messages type that omits batches
this.messages = makeMessagesResource(this);
// @ts-expect-error are using a different Beta type that omits batches
this.beta = makeBetaResource(this);
// @ts-expect-error Anthropic Foundry does not support models endpoint
this.models = undefined;
}
async authHeaders() {
if (typeof this._options.apiKey === 'function') {
let token;
try {
token = await this._options.apiKey();
}
catch (err) {
if (err instanceof Errors.AnthropicError)
throw err;
throw new Errors.AnthropicError(`Failed to get token from azureADTokenProvider: ${err.message}`,
// @ts-ignore
{ cause: err });
}
if (typeof token !== 'string' || !token) {
throw new Errors.AnthropicError(`Expected azureADTokenProvider function argument to return a string but it returned ${token}`);
}
return buildHeaders([{ Authorization: `Bearer ${token}` }]);
}
if (typeof this._options.apiKey === 'string') {
return buildHeaders([{ 'x-api-key': this.apiKey }]);
}
return undefined;
}
validateHeaders() {
return;
}
}
function makeMessagesResource(client) {
const resource = new Resources.Messages(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.batches;
return resource;
}
function makeBetaResource(client) {
const resource = new Resources.Beta(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.messages.batches;
return resource;
}
//# sourceMappingURL=client.mjs.map

View File

@@ -1,2 +0,0 @@
export * from '@anthropic-ai/sdk/core/error';
//# sourceMappingURL=error.mjs.map

View File

@@ -1,3 +0,0 @@
export * from "./client.mjs";
export { AnthropicFoundry as default } from "./client.mjs";
//# sourceMappingURL=index.mjs.map

View File

@@ -1,74 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from "./utils/values.mjs";
const brand_privateNullableHeaders = Symbol.for('brand.privateNullableHeaders');
function* iterateHeaders(headers) {
if (!headers)
return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter;
if (headers instanceof Headers) {
iter = headers.entries();
}
else if (isReadonlyArray(headers)) {
iter = headers;
}
else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string')
throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined)
continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders) => {
const targetHeaders = new Headers();
const nullHeaders = new Set();
for (const headers of newHeaders) {
const seenHeaders = new Set();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
}
else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers) => {
for (const _ of iterateHeaders(headers))
return false;
return true;
};
//# sourceMappingURL=headers.mjs.map

View File

@@ -1,8 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from "./utils/values.mjs";
export * from "./utils/base64.mjs";
export * from "./utils/env.mjs";
export * from "./utils/log.mjs";
export * from "./utils/uuid.mjs";
export * from "./utils/sleep.mjs";
//# sourceMappingURL=utils.mjs.map

View File

@@ -1,33 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../core/error.mjs";
import { encodeUTF8 } from "./bytes.mjs";
export const toBase64 = (data) => {
if (!data)
return '';
if (typeof globalThis.Buffer !== 'undefined') {
return globalThis.Buffer.from(data).toString('base64');
}
if (typeof data === 'string') {
data = encodeUTF8(data);
}
if (typeof btoa !== 'undefined') {
return btoa(String.fromCharCode.apply(null, data));
}
throw new AnthropicError('Cannot generate base64 string; Expected `Buffer` or `btoa` to be defined');
};
export const fromBase64 = (str) => {
if (typeof globalThis.Buffer !== 'undefined') {
const buf = globalThis.Buffer.from(str, 'base64');
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
}
if (typeof atob !== 'undefined') {
const bstr = atob(str);
const buf = new Uint8Array(bstr.length);
for (let i = 0; i < bstr.length; i++) {
buf[i] = bstr.charCodeAt(i);
}
return buf;
}
throw new AnthropicError('Cannot decode base64 string; Expected `Buffer` or `atob` to be defined');
};
//# sourceMappingURL=base64.mjs.map

View File

@@ -1,18 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env) => {
if (typeof globalThis.process !== 'undefined') {
return globalThis.process.env?.[env]?.trim() ?? undefined;
}
if (typeof globalThis.Deno !== 'undefined') {
return globalThis.Deno.env?.get?.(env)?.trim();
}
return undefined;
};
//# sourceMappingURL=env.mjs.map

View File

@@ -1,80 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from "./values.mjs";
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (maybeLevel, sourceName, client) => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
return undefined;
};
function noop() { }
function makeLogFn(fnLevel, logger, logLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
}
else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = /* @__PURE__ */ new WeakMap();
export function loggerFor(client) {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
name,
(name.toLowerCase() === 'x-api-key' ||
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie') ?
'***'
: value,
]));
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};
//# sourceMappingURL=log.mjs.map

View File

@@ -1 +0,0 @@
export function sleep(ms) { return new Promise(r => setTimeout(r, ms)) }

View File

@@ -1,2 +0,0 @@
import { randomUUID } from 'crypto'
export function generateUUID() { return randomUUID() }

View File

@@ -1,100 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../core/error.mjs";
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url) => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val) => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray;
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x) {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj) {
if (!obj)
return true;
for (const _k in obj)
return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj) {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = (value) => {
if (value == null) {
throw new AnthropicError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name, n) => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new AnthropicError(`${name} must be an integer`);
}
if (n < 0) {
throw new AnthropicError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value) => {
if (typeof value === 'number')
return Math.round(value);
if (typeof value === 'string')
return parseInt(value, 10);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value) => {
if (typeof value === 'number')
return value;
if (typeof value === 'string')
return parseFloat(value);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value) => {
if (typeof value === 'boolean')
return value;
if (typeof value === 'string')
return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value) => {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value) => {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value) => {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text) => {
try {
return JSON.parse(text);
}
catch (err) {
return undefined;
}
};
// Gets a value from an object, deletes the key, and returns the value (or undefined if not found)
export const pop = (obj, key) => {
const value = obj[key];
delete obj[key];
return value;
};
//# sourceMappingURL=values.mjs.map

View File

@@ -1,223 +0,0 @@
const tokenize = (input) => {
let current = 0;
let tokens = [];
while (current < input.length) {
let char = input[current];
if (char === '\\') {
current++;
continue;
}
if (char === '{') {
tokens.push({
type: 'brace',
value: '{',
});
current++;
continue;
}
if (char === '}') {
tokens.push({
type: 'brace',
value: '}',
});
current++;
continue;
}
if (char === '[') {
tokens.push({
type: 'paren',
value: '[',
});
current++;
continue;
}
if (char === ']') {
tokens.push({
type: 'paren',
value: ']',
});
current++;
continue;
}
if (char === ':') {
tokens.push({
type: 'separator',
value: ':',
});
current++;
continue;
}
if (char === ',') {
tokens.push({
type: 'delimiter',
value: ',',
});
current++;
continue;
}
if (char === '"') {
let value = '';
let danglingQuote = false;
char = input[++current];
while (char !== '"') {
if (current === input.length) {
danglingQuote = true;
break;
}
if (char === '\\') {
current++;
if (current === input.length) {
danglingQuote = true;
break;
}
value += char + input[current];
char = input[++current];
}
else {
value += char;
char = input[++current];
}
}
char = input[++current];
if (!danglingQuote) {
tokens.push({
type: 'string',
value,
});
}
continue;
}
let WHITESPACE = /\s/;
if (char && WHITESPACE.test(char)) {
current++;
continue;
}
let NUMBERS = /[0-9]/;
if ((char && NUMBERS.test(char)) || char === '-' || char === '.') {
let value = '';
if (char === '-') {
value += char;
char = input[++current];
}
while ((char && NUMBERS.test(char)) || char === '.') {
value += char;
char = input[++current];
}
tokens.push({
type: 'number',
value,
});
continue;
}
let LETTERS = /[a-z]/i;
if (char && LETTERS.test(char)) {
let value = '';
while (char && LETTERS.test(char)) {
if (current === input.length) {
break;
}
value += char;
char = input[++current];
}
if (value == 'true' || value == 'false' || value === 'null') {
tokens.push({
type: 'name',
value,
});
}
else {
// unknown token, e.g. `nul` which isn't quite `null`
current++;
continue;
}
continue;
}
current++;
}
return tokens;
}, strip = (tokens) => {
if (tokens.length === 0) {
return tokens;
}
let lastToken = tokens[tokens.length - 1];
switch (lastToken.type) {
case 'separator':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
case 'number':
let lastCharacterOfLastToken = lastToken.value[lastToken.value.length - 1];
if (lastCharacterOfLastToken === '.' || lastCharacterOfLastToken === '-') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
case 'string':
let tokenBeforeTheLastToken = tokens[tokens.length - 2];
if (tokenBeforeTheLastToken?.type === 'delimiter') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
else if (tokenBeforeTheLastToken?.type === 'brace' && tokenBeforeTheLastToken.value === '{') {
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
}
break;
case 'delimiter':
tokens = tokens.slice(0, tokens.length - 1);
return strip(tokens);
break;
}
return tokens;
}, unstrip = (tokens) => {
let tail = [];
tokens.map((token) => {
if (token.type === 'brace') {
if (token.value === '{') {
tail.push('}');
}
else {
tail.splice(tail.lastIndexOf('}'), 1);
}
}
if (token.type === 'paren') {
if (token.value === '[') {
tail.push(']');
}
else {
tail.splice(tail.lastIndexOf(']'), 1);
}
}
});
if (tail.length > 0) {
tail.reverse().map((item) => {
if (item === '}') {
tokens.push({
type: 'brace',
value: '}',
});
}
else if (item === ']') {
tokens.push({
type: 'paren',
value: ']',
});
}
});
}
return tokens;
}, generate = (tokens) => {
let output = '';
tokens.map((token) => {
switch (token.type) {
case 'string':
output += '"' + token.value + '"';
break;
default:
output += token.value;
break;
}
});
return output;
}, partialParse = (input) => JSON.parse(generate(unstrip(strip(tokenize(input)))));
export { partialParse };
//# sourceMappingURL=parser.mjs.map

View File

@@ -1,559 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
var _BaseAnthropic_instances, _a, _BaseAnthropic_encoder, _BaseAnthropic_baseURLOverridden;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "./internal/tslib.mjs";
import { uuid4 } from "./internal/utils/uuid.mjs";
import { validatePositiveInteger, isAbsoluteURL, safeJSON } from "./internal/utils/values.mjs";
import { sleep } from "./internal/utils/sleep.mjs";
import { castToError, isAbortError } from "./internal/errors.mjs";
import { getPlatformHeaders } from "./internal/detect-platform.mjs";
import * as Shims from "./internal/shims.mjs";
import * as Opts from "./internal/request-options.mjs";
import { VERSION } from "./version.mjs";
import * as Errors from "./core/error.mjs";
import * as Pagination from "./core/pagination.mjs";
import * as Uploads from "./core/uploads.mjs";
import * as API from "./resources/index.mjs";
import { APIPromise } from "./core/api-promise.mjs";
import { Completions, } from "./resources/completions.mjs";
import { Models } from "./resources/models.mjs";
import { Beta, } from "./resources/beta/beta.mjs";
import { Messages, } from "./resources/messages/messages.mjs";
import { isRunningInBrowser } from "./internal/detect-platform.mjs";
import { buildHeaders } from "./internal/headers.mjs";
import { readEnv } from "./internal/utils/env.mjs";
import { formatRequestDetails, loggerFor, parseLogLevel, } from "./internal/utils/log.mjs";
import { isEmptyObj } from "./internal/utils/values.mjs";
export const HUMAN_PROMPT = '\\n\\nHuman:';
export const AI_PROMPT = '\\n\\nAssistant:';
/**
* Base class for Anthropic API clients.
*/
export class BaseAnthropic {
/**
* API Client for interfacing with the Anthropic API.
*
* @param {string | null | undefined} [opts.apiKey=process.env['ANTHROPIC_API_KEY'] ?? null]
* @param {string | null | undefined} [opts.authToken=process.env['ANTHROPIC_AUTH_TOKEN'] ?? null]
* @param {string} [opts.baseURL=process.env['ANTHROPIC_BASE_URL'] ?? https://api.anthropic.com] - Override the default base URL for the API.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor({ baseURL = readEnv('ANTHROPIC_BASE_URL'), apiKey = readEnv('ANTHROPIC_API_KEY') ?? null, authToken = readEnv('ANTHROPIC_AUTH_TOKEN') ?? null, ...opts } = {}) {
_BaseAnthropic_instances.add(this);
_BaseAnthropic_encoder.set(this, void 0);
const options = {
apiKey,
authToken,
...opts,
baseURL: baseURL || `https://api.anthropic.com`,
};
if (!options.dangerouslyAllowBrowser && isRunningInBrowser()) {
throw new Errors.AnthropicError("It looks like you're running in a browser-like environment.\n\nThis is disabled by default, as it risks exposing your secret API credentials to attackers.\nIf you understand the risks and have appropriate mitigations in place,\nyou can set the `dangerouslyAllowBrowser` option to `true`, e.g.,\n\nnew Anthropic({ apiKey, dangerouslyAllowBrowser: true });\n");
}
this.baseURL = options.baseURL;
this.timeout = options.timeout ?? _a.DEFAULT_TIMEOUT /* 10 minutes */;
this.logger = options.logger ?? console;
const defaultLogLevel = 'warn';
// Set default logLevel early so that we can log a warning in parseLogLevel.
this.logLevel = defaultLogLevel;
this.logLevel =
parseLogLevel(options.logLevel, 'ClientOptions.logLevel', this) ??
parseLogLevel(readEnv('ANTHROPIC_LOG'), "process.env['ANTHROPIC_LOG']", this) ??
defaultLogLevel;
this.fetchOptions = options.fetchOptions;
this.maxRetries = options.maxRetries ?? 2;
this.fetch = options.fetch ?? Shims.getDefaultFetch();
__classPrivateFieldSet(this, _BaseAnthropic_encoder, Opts.FallbackEncoder, "f");
this._options = options;
this.apiKey = typeof apiKey === 'string' ? apiKey : null;
this.authToken = authToken;
}
/**
* Create a new client instance re-using the same options given to the current client with optional overriding.
*/
withOptions(options) {
const client = new this.constructor({
...this._options,
baseURL: this.baseURL,
maxRetries: this.maxRetries,
timeout: this.timeout,
logger: this.logger,
logLevel: this.logLevel,
fetch: this.fetch,
fetchOptions: this.fetchOptions,
apiKey: this.apiKey,
authToken: this.authToken,
...options,
});
return client;
}
defaultQuery() {
return this._options.defaultQuery;
}
validateHeaders({ values, nulls }) {
if (values.get('x-api-key') || values.get('authorization')) {
return;
}
if (this.apiKey && values.get('x-api-key')) {
return;
}
if (nulls.has('x-api-key')) {
return;
}
if (this.authToken && values.get('authorization')) {
return;
}
if (nulls.has('authorization')) {
return;
}
throw new Error('Could not resolve authentication method. Expected either apiKey or authToken to be set. Or for one of the "X-Api-Key" or "Authorization" headers to be explicitly omitted');
}
async authHeaders(opts) {
return buildHeaders([await this.apiKeyAuth(opts), await this.bearerAuth(opts)]);
}
async apiKeyAuth(opts) {
if (this.apiKey == null) {
return undefined;
}
return buildHeaders([{ 'X-Api-Key': this.apiKey }]);
}
async bearerAuth(opts) {
if (this.authToken == null) {
return undefined;
}
return buildHeaders([{ Authorization: `Bearer ${this.authToken}` }]);
}
/**
* Basic re-implementation of `qs.stringify` for primitive types.
*/
stringifyQuery(query) {
return Object.entries(query)
.filter(([_, value]) => typeof value !== 'undefined')
.map(([key, value]) => {
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`;
}
if (value === null) {
return `${encodeURIComponent(key)}=`;
}
throw new Errors.AnthropicError(`Cannot stringify type ${typeof value}; Expected string, number, boolean, or null. If you need to pass nested query parameters, you can manually encode them, e.g. { query: { 'foo[key1]': value1, 'foo[key2]': value2 } }, and please open a GitHub issue requesting better support for your use case.`);
})
.join('&');
}
getUserAgent() {
return `${this.constructor.name}/JS ${VERSION}`;
}
defaultIdempotencyKey() {
return `stainless-node-retry-${uuid4()}`;
}
makeStatusError(status, error, message, headers) {
return Errors.APIError.generate(status, error, message, headers);
}
buildURL(path, query, defaultBaseURL) {
const baseURL = (!__classPrivateFieldGet(this, _BaseAnthropic_instances, "m", _BaseAnthropic_baseURLOverridden).call(this) && defaultBaseURL) || this.baseURL;
const url = isAbsoluteURL(path) ?
new URL(path)
: new URL(baseURL + (baseURL.endsWith('/') && path.startsWith('/') ? path.slice(1) : path));
const defaultQuery = this.defaultQuery();
if (!isEmptyObj(defaultQuery)) {
query = { ...defaultQuery, ...query };
}
if (typeof query === 'object' && query && !Array.isArray(query)) {
url.search = this.stringifyQuery(query);
}
return url.toString();
}
_calculateNonstreamingTimeout(maxTokens) {
const defaultTimeout = 10 * 60;
const expectedTimeout = (60 * 60 * maxTokens) / 128000;
if (expectedTimeout > defaultTimeout) {
throw new Errors.AnthropicError('Streaming is required for operations that may take longer than 10 minutes. ' +
'See https://github.com/anthropics/anthropic-sdk-typescript#streaming-responses for more details');
}
return defaultTimeout * 1000;
}
/**
* Used as a callback for mutating the given `FinalRequestOptions` object.
*/
async prepareOptions(options) { }
/**
* Used as a callback for mutating the given `RequestInit` object.
*
* This is useful for cases where you want to add certain headers based off of
* the request properties, e.g. `method` or `url`.
*/
async prepareRequest(request, { url, options }) { }
get(path, opts) {
return this.methodRequest('get', path, opts);
}
post(path, opts) {
return this.methodRequest('post', path, opts);
}
patch(path, opts) {
return this.methodRequest('patch', path, opts);
}
put(path, opts) {
return this.methodRequest('put', path, opts);
}
delete(path, opts) {
return this.methodRequest('delete', path, opts);
}
methodRequest(method, path, opts) {
return this.request(Promise.resolve(opts).then((opts) => {
return { method, path, ...opts };
}));
}
request(options, remainingRetries = null) {
return new APIPromise(this, this.makeRequest(options, remainingRetries, undefined));
}
async makeRequest(optionsInput, retriesRemaining, retryOfRequestLogID) {
const options = await optionsInput;
const maxRetries = options.maxRetries ?? this.maxRetries;
if (retriesRemaining == null) {
retriesRemaining = maxRetries;
}
await this.prepareOptions(options);
const { req, url, timeout } = await this.buildRequest(options, {
retryCount: maxRetries - retriesRemaining,
});
await this.prepareRequest(req, { url, options });
/** Not an API request ID, just for correlating local log entries. */
const requestLogID = 'log_' + ((Math.random() * (1 << 24)) | 0).toString(16).padStart(6, '0');
const retryLogStr = retryOfRequestLogID === undefined ? '' : `, retryOf: ${retryOfRequestLogID}`;
const startTime = Date.now();
loggerFor(this).debug(`[${requestLogID}] sending request`, formatRequestDetails({
retryOfRequestLogID,
method: options.method,
url,
options,
headers: req.headers,
}));
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
const controller = new AbortController();
const response = await this.fetchWithTimeout(url, req, timeout, controller).catch(castToError);
const headersTime = Date.now();
if (response instanceof globalThis.Error) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
if (options.signal?.aborted) {
throw new Errors.APIUserAbortError();
}
// detect native connection timeout errors
// deno throws "TypeError: error sending request for url (https://example/): client error (Connect): tcp connect error: Operation timed out (os error 60): Operation timed out (os error 60)"
// undici throws "TypeError: fetch failed" with cause "ConnectTimeoutError: Connect Timeout Error (attempted address: example:443, timeout: 1ms)"
// others do not provide enough information to distinguish timeouts from other connection errors
const isTimeout = isAbortError(response) ||
/timed? ?out/i.test(String(response) + ('cause' in response ? String(response.cause) : ''));
if (retriesRemaining) {
loggerFor(this).info(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - ${retryMessage}`);
loggerFor(this).debug(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (${retryMessage})`, formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}));
return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID);
}
loggerFor(this).info(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} - error; no more retries left`);
loggerFor(this).debug(`[${requestLogID}] connection ${isTimeout ? 'timed out' : 'failed'} (error; no more retries left)`, formatRequestDetails({
retryOfRequestLogID,
url,
durationMs: headersTime - startTime,
message: response.message,
}));
if (isTimeout) {
throw new Errors.APIConnectionTimeoutError();
}
throw new Errors.APIConnectionError({ cause: response });
}
const specialHeaders = [...response.headers.entries()]
.filter(([name]) => name === 'request-id')
.map(([name, value]) => ', ' + name + ': ' + JSON.stringify(value))
.join('');
const responseInfo = `[${requestLogID}${retryLogStr}${specialHeaders}] ${req.method} ${url} ${response.ok ? 'succeeded' : 'failed'} with status ${response.status} in ${headersTime - startTime}ms`;
if (!response.ok) {
const shouldRetry = await this.shouldRetry(response);
if (retriesRemaining && shouldRetry) {
const retryMessage = `retrying, ${retriesRemaining} attempts remaining`;
// We don't need the body of this response.
await Shims.CancelReadableStream(response.body);
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
loggerFor(this).debug(`[${requestLogID}] response error (${retryMessage})`, formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}));
return this.retryRequest(options, retriesRemaining, retryOfRequestLogID ?? requestLogID, response.headers);
}
const retryMessage = shouldRetry ? `error; no more retries left` : `error; not retryable`;
loggerFor(this).info(`${responseInfo} - ${retryMessage}`);
const errText = await response.text().catch((err) => castToError(err).message);
const errJSON = safeJSON(errText);
const errMessage = errJSON ? undefined : errText;
loggerFor(this).debug(`[${requestLogID}] response error (${retryMessage})`, formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
message: errMessage,
durationMs: Date.now() - startTime,
}));
const err = this.makeStatusError(response.status, errJSON, errMessage, response.headers);
throw err;
}
loggerFor(this).info(responseInfo);
loggerFor(this).debug(`[${requestLogID}] response start`, formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
headers: response.headers,
durationMs: headersTime - startTime,
}));
return { response, options, controller, requestLogID, retryOfRequestLogID, startTime };
}
getAPIList(path, Page, opts) {
return this.requestAPIList(Page, opts && 'then' in opts ?
opts.then((opts) => ({ method: 'get', path, ...opts }))
: { method: 'get', path, ...opts });
}
requestAPIList(Page, options) {
const request = this.makeRequest(options, null, undefined);
return new Pagination.PagePromise(this, request, Page);
}
async fetchWithTimeout(url, init, ms, controller) {
const { signal, method, ...options } = init || {};
// Avoid creating a closure over `this`, `init`, or `options` to prevent memory leaks.
// An arrow function like `() => controller.abort()` captures the surrounding scope,
// which includes the request body and other large objects. When the user passes a
// long-lived AbortSignal, the listener prevents those objects from being GC'd for
// the lifetime of the signal. Using `.bind()` only retains a reference to the
// controller itself.
const abort = this._makeAbort(controller);
if (signal)
signal.addEventListener('abort', abort, { once: true });
const timeout = setTimeout(abort, ms);
const isReadableBody = (globalThis.ReadableStream && options.body instanceof globalThis.ReadableStream) ||
(typeof options.body === 'object' && options.body !== null && Symbol.asyncIterator in options.body);
const fetchOptions = {
signal: controller.signal,
...(isReadableBody ? { duplex: 'half' } : {}),
method: 'GET',
...options,
};
if (method) {
// Custom methods like 'patch' need to be uppercased
// See https://github.com/nodejs/undici/issues/2294
fetchOptions.method = method.toUpperCase();
}
try {
// use undefined this binding; fetch errors if bound to something else in browser/cloudflare
return await this.fetch.call(undefined, url, fetchOptions);
}
finally {
clearTimeout(timeout);
}
}
async shouldRetry(response) {
// Note this is not a standard header.
const shouldRetryHeader = response.headers.get('x-should-retry');
// If the server explicitly says whether or not to retry, obey.
if (shouldRetryHeader === 'true')
return true;
if (shouldRetryHeader === 'false')
return false;
// Retry on request timeouts.
if (response.status === 408)
return true;
// Retry on lock timeouts.
if (response.status === 409)
return true;
// Retry on rate limits.
if (response.status === 429)
return true;
// Retry internal errors.
if (response.status >= 500)
return true;
return false;
}
async retryRequest(options, retriesRemaining, requestLogID, responseHeaders) {
let timeoutMillis;
// Note the `retry-after-ms` header may not be standard, but is a good idea and we'd like proactive support for it.
const retryAfterMillisHeader = responseHeaders?.get('retry-after-ms');
if (retryAfterMillisHeader) {
const timeoutMs = parseFloat(retryAfterMillisHeader);
if (!Number.isNaN(timeoutMs)) {
timeoutMillis = timeoutMs;
}
}
// About the Retry-After header: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Retry-After
const retryAfterHeader = responseHeaders?.get('retry-after');
if (retryAfterHeader && !timeoutMillis) {
const timeoutSeconds = parseFloat(retryAfterHeader);
if (!Number.isNaN(timeoutSeconds)) {
timeoutMillis = timeoutSeconds * 1000;
}
else {
timeoutMillis = Date.parse(retryAfterHeader) - Date.now();
}
}
// If the API asks us to wait a certain amount of time (and it's a reasonable amount),
// just do what it says, but otherwise calculate a default
if (!(timeoutMillis && 0 <= timeoutMillis && timeoutMillis < 60 * 1000)) {
const maxRetries = options.maxRetries ?? this.maxRetries;
timeoutMillis = this.calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries);
}
await sleep(timeoutMillis);
return this.makeRequest(options, retriesRemaining - 1, requestLogID);
}
calculateDefaultRetryTimeoutMillis(retriesRemaining, maxRetries) {
const initialRetryDelay = 0.5;
const maxRetryDelay = 8.0;
const numRetries = maxRetries - retriesRemaining;
// Apply exponential backoff, but not more than the max.
const sleepSeconds = Math.min(initialRetryDelay * Math.pow(2, numRetries), maxRetryDelay);
// Apply some jitter, take up to at most 25 percent of the retry time.
const jitter = 1 - Math.random() * 0.25;
return sleepSeconds * jitter * 1000;
}
calculateNonstreamingTimeout(maxTokens, maxNonstreamingTokens) {
const maxTime = 60 * 60 * 1000; // 60 minutes
const defaultTime = 60 * 10 * 1000; // 10 minutes
const expectedTime = (maxTime * maxTokens) / 128000;
if (expectedTime > defaultTime || (maxNonstreamingTokens != null && maxTokens > maxNonstreamingTokens)) {
throw new Errors.AnthropicError('Streaming is required for operations that may take longer than 10 minutes. See https://github.com/anthropics/anthropic-sdk-typescript#long-requests for more details');
}
return defaultTime;
}
async buildRequest(inputOptions, { retryCount = 0 } = {}) {
const options = { ...inputOptions };
const { method, path, query, defaultBaseURL } = options;
const url = this.buildURL(path, query, defaultBaseURL);
if ('timeout' in options)
validatePositiveInteger('timeout', options.timeout);
options.timeout = options.timeout ?? this.timeout;
const { bodyHeaders, body } = this.buildBody({ options });
const reqHeaders = await this.buildHeaders({ options: inputOptions, method, bodyHeaders, retryCount });
const req = {
method,
headers: reqHeaders,
...(options.signal && { signal: options.signal }),
...(globalThis.ReadableStream &&
body instanceof globalThis.ReadableStream && { duplex: 'half' }),
...(body && { body }),
...(this.fetchOptions ?? {}),
...(options.fetchOptions ?? {}),
};
return { req, url, timeout: options.timeout };
}
async buildHeaders({ options, method, bodyHeaders, retryCount, }) {
let idempotencyHeaders = {};
if (this.idempotencyHeader && method !== 'get') {
if (!options.idempotencyKey)
options.idempotencyKey = this.defaultIdempotencyKey();
idempotencyHeaders[this.idempotencyHeader] = options.idempotencyKey;
}
const headers = buildHeaders([
idempotencyHeaders,
{
Accept: 'application/json',
'User-Agent': this.getUserAgent(),
'X-Stainless-Retry-Count': String(retryCount),
...(options.timeout ? { 'X-Stainless-Timeout': String(Math.trunc(options.timeout / 1000)) } : {}),
...getPlatformHeaders(),
...(this._options.dangerouslyAllowBrowser ?
{ 'anthropic-dangerous-direct-browser-access': 'true' }
: undefined),
'anthropic-version': '2023-06-01',
},
await this.authHeaders(options),
this._options.defaultHeaders,
bodyHeaders,
options.headers,
]);
this.validateHeaders(headers);
return headers.values;
}
_makeAbort(controller) {
// note: we can't just inline this method inside `fetchWithTimeout()` because then the closure
// would capture all request options, and cause a memory leak.
return () => controller.abort();
}
buildBody({ options: { body, headers: rawHeaders } }) {
if (!body) {
return { bodyHeaders: undefined, body: undefined };
}
const headers = buildHeaders([rawHeaders]);
if (
// Pass raw type verbatim
ArrayBuffer.isView(body) ||
body instanceof ArrayBuffer ||
body instanceof DataView ||
(typeof body === 'string' &&
// Preserve legacy string encoding behavior for now
headers.values.has('content-type')) ||
// `Blob` is superset of `File`
(globalThis.Blob && body instanceof globalThis.Blob) ||
// `FormData` -> `multipart/form-data`
body instanceof FormData ||
// `URLSearchParams` -> `application/x-www-form-urlencoded`
body instanceof URLSearchParams ||
// Send chunked stream (each chunk has own `length`)
(globalThis.ReadableStream && body instanceof globalThis.ReadableStream)) {
return { bodyHeaders: undefined, body: body };
}
else if (typeof body === 'object' &&
(Symbol.asyncIterator in body ||
(Symbol.iterator in body && 'next' in body && typeof body.next === 'function'))) {
return { bodyHeaders: undefined, body: Shims.ReadableStreamFrom(body) };
}
else {
return __classPrivateFieldGet(this, _BaseAnthropic_encoder, "f").call(this, { body, headers });
}
}
}
_a = BaseAnthropic, _BaseAnthropic_encoder = new WeakMap(), _BaseAnthropic_instances = new WeakSet(), _BaseAnthropic_baseURLOverridden = function _BaseAnthropic_baseURLOverridden() {
return this.baseURL !== 'https://api.anthropic.com';
};
BaseAnthropic.Anthropic = _a;
BaseAnthropic.HUMAN_PROMPT = HUMAN_PROMPT;
BaseAnthropic.AI_PROMPT = AI_PROMPT;
BaseAnthropic.DEFAULT_TIMEOUT = 600000; // 10 minutes
BaseAnthropic.AnthropicError = Errors.AnthropicError;
BaseAnthropic.APIError = Errors.APIError;
BaseAnthropic.APIConnectionError = Errors.APIConnectionError;
BaseAnthropic.APIConnectionTimeoutError = Errors.APIConnectionTimeoutError;
BaseAnthropic.APIUserAbortError = Errors.APIUserAbortError;
BaseAnthropic.NotFoundError = Errors.NotFoundError;
BaseAnthropic.ConflictError = Errors.ConflictError;
BaseAnthropic.RateLimitError = Errors.RateLimitError;
BaseAnthropic.BadRequestError = Errors.BadRequestError;
BaseAnthropic.AuthenticationError = Errors.AuthenticationError;
BaseAnthropic.InternalServerError = Errors.InternalServerError;
BaseAnthropic.PermissionDeniedError = Errors.PermissionDeniedError;
BaseAnthropic.UnprocessableEntityError = Errors.UnprocessableEntityError;
BaseAnthropic.toFile = Uploads.toFile;
/**
* API Client for interfacing with the Anthropic API.
*/
export class Anthropic extends BaseAnthropic {
constructor() {
super(...arguments);
this.completions = new API.Completions(this);
this.messages = new API.Messages(this);
this.models = new API.Models(this);
this.beta = new API.Beta(this);
}
}
Anthropic.Completions = Completions;
Anthropic.Messages = Messages;
Anthropic.Models = Models;
Anthropic.Beta = Beta;
//# sourceMappingURL=client.mjs.map

View File

@@ -1,72 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
var _APIPromise_client;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../internal/tslib.mjs";
import { defaultParseResponse, addRequestID, } from "../internal/parse.mjs";
/**
* A subclass of `Promise` providing additional helper methods
* for interacting with the SDK.
*/
export class APIPromise extends Promise {
constructor(client, responsePromise, parseResponse = defaultParseResponse) {
super((resolve) => {
// this is maybe a bit weird but this has to be a no-op to not implicitly
// parse the response body; instead .then, .catch, .finally are overridden
// to parse the response
resolve(null);
});
this.responsePromise = responsePromise;
this.parseResponse = parseResponse;
_APIPromise_client.set(this, void 0);
__classPrivateFieldSet(this, _APIPromise_client, client, "f");
}
_thenUnwrap(transform) {
return new APIPromise(__classPrivateFieldGet(this, _APIPromise_client, "f"), this.responsePromise, async (client, props) => addRequestID(transform(await this.parseResponse(client, props), props), props.response));
}
/**
* Gets the raw `Response` instance instead of parsing the response
* data.
*
* If you want to parse the response body but still get the `Response`
* instance, you can use {@link withResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
asResponse() {
return this.responsePromise.then((p) => p.response);
}
/**
* Gets the parsed response data, the raw `Response` instance and the ID of the request,
* returned via the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* If you just want to get the raw `Response` instance without parsing it,
* you can use {@link asResponse()}.
*
* 👋 Getting the wrong TypeScript type for `Response`?
* Try setting `"moduleResolution": "NodeNext"` or add `"lib": ["DOM"]`
* to your `tsconfig.json`.
*/
async withResponse() {
const [data, response] = await Promise.all([this.parse(), this.asResponse()]);
return { data, response, request_id: response.headers.get('request-id') };
}
parse() {
if (!this.parsedPromise) {
this.parsedPromise = this.responsePromise.then((data) => this.parseResponse(__classPrivateFieldGet(this, _APIPromise_client, "f"), data));
}
return this.parsedPromise;
}
then(onfulfilled, onrejected) {
return this.parse().then(onfulfilled, onrejected);
}
catch(onrejected) {
return this.parse().catch(onrejected);
}
finally(onfinally) {
return this.parse().finally(onfinally);
}
}
_APIPromise_client = new WeakMap();
//# sourceMappingURL=api-promise.mjs.map

View File

@@ -1,98 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { castToError } from "../internal/errors.mjs";
export class AnthropicError extends Error {
}
export class APIError extends AnthropicError {
constructor(status, error, message, headers) {
super(`${APIError.makeMessage(status, error, message)}`);
this.status = status;
this.headers = headers;
this.requestID = headers?.get('request-id');
this.error = error;
}
static makeMessage(status, error, message) {
const msg = error?.message ?
typeof error.message === 'string' ?
error.message
: JSON.stringify(error.message)
: error ? JSON.stringify(error)
: message;
if (status && msg) {
return `${status} ${msg}`;
}
if (status) {
return `${status} status code (no body)`;
}
if (msg) {
return msg;
}
return '(no status code or body)';
}
static generate(status, errorResponse, message, headers) {
if (!status || !headers) {
return new APIConnectionError({ message, cause: castToError(errorResponse) });
}
const error = errorResponse;
if (status === 400) {
return new BadRequestError(status, error, message, headers);
}
if (status === 401) {
return new AuthenticationError(status, error, message, headers);
}
if (status === 403) {
return new PermissionDeniedError(status, error, message, headers);
}
if (status === 404) {
return new NotFoundError(status, error, message, headers);
}
if (status === 409) {
return new ConflictError(status, error, message, headers);
}
if (status === 422) {
return new UnprocessableEntityError(status, error, message, headers);
}
if (status === 429) {
return new RateLimitError(status, error, message, headers);
}
if (status >= 500) {
return new InternalServerError(status, error, message, headers);
}
return new APIError(status, error, message, headers);
}
}
export class APIUserAbortError extends APIError {
constructor({ message } = {}) {
super(undefined, undefined, message || 'Request was aborted.', undefined);
}
}
export class APIConnectionError extends APIError {
constructor({ message, cause }) {
super(undefined, undefined, message || 'Connection error.', undefined);
// in some environments the 'cause' property is already declared
// @ts-ignore
if (cause)
this.cause = cause;
}
}
export class APIConnectionTimeoutError extends APIConnectionError {
constructor({ message } = {}) {
super({ message: message ?? 'Request timed out.' });
}
}
export class BadRequestError extends APIError {
}
export class AuthenticationError extends APIError {
}
export class PermissionDeniedError extends APIError {
}
export class NotFoundError extends APIError {
}
export class ConflictError extends APIError {
}
export class UnprocessableEntityError extends APIError {
}
export class RateLimitError extends APIError {
}
export class InternalServerError extends APIError {
}
//# sourceMappingURL=error.mjs.map

View File

@@ -1,177 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
var _AbstractPage_client;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../internal/tslib.mjs";
import { AnthropicError } from "./error.mjs";
import { defaultParseResponse } from "../internal/parse.mjs";
import { APIPromise } from "./api-promise.mjs";
import { maybeObj } from "../internal/utils/values.mjs";
export class AbstractPage {
constructor(client, response, body, options) {
_AbstractPage_client.set(this, void 0);
__classPrivateFieldSet(this, _AbstractPage_client, client, "f");
this.options = options;
this.response = response;
this.body = body;
}
hasNextPage() {
const items = this.getPaginatedItems();
if (!items.length)
return false;
return this.nextPageRequestOptions() != null;
}
async getNextPage() {
const nextOptions = this.nextPageRequestOptions();
if (!nextOptions) {
throw new AnthropicError('No next page expected; please check `.hasNextPage()` before calling `.getNextPage()`.');
}
return await __classPrivateFieldGet(this, _AbstractPage_client, "f").requestAPIList(this.constructor, nextOptions);
}
async *iterPages() {
let page = this;
yield page;
while (page.hasNextPage()) {
page = await page.getNextPage();
yield page;
}
}
async *[(_AbstractPage_client = new WeakMap(), Symbol.asyncIterator)]() {
for await (const page of this.iterPages()) {
for (const item of page.getPaginatedItems()) {
yield item;
}
}
}
}
/**
* This subclass of Promise will resolve to an instantiated Page once the request completes.
*
* It also implements AsyncIterable to allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
export class PagePromise extends APIPromise {
constructor(client, request, Page) {
super(client, request, async (client, props) => new Page(client, props.response, await defaultParseResponse(client, props), props.options));
}
/**
* Allow auto-paginating iteration on an unawaited list call, eg:
*
* for await (const item of client.items.list()) {
* console.log(item)
* }
*/
async *[Symbol.asyncIterator]() {
const page = await this;
for await (const item of page) {
yield item;
}
}
}
export class Page extends AbstractPage {
constructor(client, response, body, options) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.first_id = body.first_id || null;
this.last_id = body.last_id || null;
}
getPaginatedItems() {
return this.data ?? [];
}
hasNextPage() {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions() {
if (this.options.query?.['before_id']) {
// in reverse
const first_id = this.first_id;
if (!first_id) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
before_id: first_id,
},
};
}
const cursor = this.last_id;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
after_id: cursor,
},
};
}
}
export class TokenPage extends AbstractPage {
constructor(client, response, body, options) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.next_page = body.next_page || null;
}
getPaginatedItems() {
return this.data ?? [];
}
hasNextPage() {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions() {
const cursor = this.next_page;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
page_token: cursor,
},
};
}
}
export class PageCursor extends AbstractPage {
constructor(client, response, body, options) {
super(client, response, body, options);
this.data = body.data || [];
this.has_more = body.has_more || false;
this.next_page = body.next_page || null;
}
getPaginatedItems() {
return this.data ?? [];
}
hasNextPage() {
if (this.has_more === false) {
return false;
}
return super.hasNextPage();
}
nextPageRequestOptions() {
const cursor = this.next_page;
if (!cursor) {
return null;
}
return {
...this.options,
query: {
...maybeObj(this.options.query),
page: cursor,
},
};
}
}
//# sourceMappingURL=pagination.mjs.map

View File

@@ -1,7 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export class APIResource {
constructor(client) {
this._client = client;
}
}
//# sourceMappingURL=resource.mjs.map

View File

@@ -1,283 +0,0 @@
var _Stream_client;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../internal/tslib.mjs";
import { AnthropicError } from "./error.mjs";
import { makeReadableStream } from "../internal/shims.mjs";
import { findDoubleNewlineIndex, LineDecoder } from "../internal/decoders/line.mjs";
import { ReadableStreamToAsyncIterable } from "../internal/shims.mjs";
import { isAbortError } from "../internal/errors.mjs";
import { safeJSON } from "../internal/utils/values.mjs";
import { encodeUTF8 } from "../internal/utils/bytes.mjs";
import { loggerFor } from "../internal/utils/log.mjs";
import { APIError } from "./error.mjs";
export class Stream {
constructor(iterator, controller, client) {
this.iterator = iterator;
_Stream_client.set(this, void 0);
this.controller = controller;
__classPrivateFieldSet(this, _Stream_client, client, "f");
}
static fromSSEResponse(response, controller, client) {
let consumed = false;
const logger = client ? loggerFor(client) : console;
async function* iterator() {
if (consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const sse of _iterSSEMessages(response, controller)) {
if (sse.event === 'completion') {
try {
yield JSON.parse(sse.data);
}
catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
if (sse.event === 'message_start' ||
sse.event === 'message_delta' ||
sse.event === 'message_stop' ||
sse.event === 'content_block_start' ||
sse.event === 'content_block_delta' ||
sse.event === 'content_block_stop') {
try {
yield JSON.parse(sse.data);
}
catch (e) {
logger.error(`Could not parse message into JSON:`, sse.data);
logger.error(`From chunk:`, sse.raw);
throw e;
}
}
if (sse.event === 'ping') {
continue;
}
if (sse.event === 'error') {
throw new APIError(undefined, safeJSON(sse.data) ?? sse.data, undefined, response.headers);
}
}
done = true;
}
catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e))
return;
throw e;
}
finally {
// If the user `break`s, abort the ongoing request.
if (!done)
controller.abort();
}
}
return new Stream(iterator, controller, client);
}
/**
* Generates a Stream from a newline-separated ReadableStream
* where each item is a JSON value.
*/
static fromReadableStream(readableStream, controller, client) {
let consumed = false;
async function* iterLines() {
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable(readableStream);
for await (const chunk of iter) {
for (const line of lineDecoder.decode(chunk)) {
yield line;
}
}
for (const line of lineDecoder.flush()) {
yield line;
}
}
async function* iterator() {
if (consumed) {
throw new AnthropicError('Cannot iterate over a consumed stream, use `.tee()` to split the stream.');
}
consumed = true;
let done = false;
try {
for await (const line of iterLines()) {
if (done)
continue;
if (line)
yield JSON.parse(line);
}
done = true;
}
catch (e) {
// If the user calls `stream.controller.abort()`, we should exit without throwing.
if (isAbortError(e))
return;
throw e;
}
finally {
// If the user `break`s, abort the ongoing request.
if (!done)
controller.abort();
}
}
return new Stream(iterator, controller, client);
}
[(_Stream_client = new WeakMap(), Symbol.asyncIterator)]() {
return this.iterator();
}
/**
* Splits the stream into two streams which can be
* independently read from at different speeds.
*/
tee() {
const left = [];
const right = [];
const iterator = this.iterator();
const teeIterator = (queue) => {
return {
next: () => {
if (queue.length === 0) {
const result = iterator.next();
left.push(result);
right.push(result);
}
return queue.shift();
},
};
};
return [
new Stream(() => teeIterator(left), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
new Stream(() => teeIterator(right), this.controller, __classPrivateFieldGet(this, _Stream_client, "f")),
];
}
/**
* Converts this stream to a newline-separated ReadableStream of
* JSON stringified values in the stream
* which can be turned back into a Stream with `Stream.fromReadableStream()`.
*/
toReadableStream() {
const self = this;
let iter;
return makeReadableStream({
async start() {
iter = self[Symbol.asyncIterator]();
},
async pull(ctrl) {
try {
const { value, done } = await iter.next();
if (done)
return ctrl.close();
const bytes = encodeUTF8(JSON.stringify(value) + '\n');
ctrl.enqueue(bytes);
}
catch (err) {
ctrl.error(err);
}
},
async cancel() {
await iter.return?.();
},
});
}
}
export async function* _iterSSEMessages(response, controller) {
if (!response.body) {
controller.abort();
if (typeof globalThis.navigator !== 'undefined' &&
globalThis.navigator.product === 'ReactNative') {
throw new AnthropicError(`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`);
}
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}
const sseDecoder = new SSEDecoder();
const lineDecoder = new LineDecoder();
const iter = ReadableStreamToAsyncIterable(response.body);
for await (const sseChunk of iterSSEChunks(iter)) {
for (const line of lineDecoder.decode(sseChunk)) {
const sse = sseDecoder.decode(line);
if (sse)
yield sse;
}
}
for (const line of lineDecoder.flush()) {
const sse = sseDecoder.decode(line);
if (sse)
yield sse;
}
}
/**
* Given an async iterable iterator, iterates over it and yields full
* SSE chunks, i.e. yields when a double new-line is encountered.
*/
async function* iterSSEChunks(iterator) {
let data = new Uint8Array();
for await (const chunk of iterator) {
if (chunk == null) {
continue;
}
const binaryChunk = chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
let newData = new Uint8Array(data.length + binaryChunk.length);
newData.set(data);
newData.set(binaryChunk, data.length);
data = newData;
let patternIndex;
while ((patternIndex = findDoubleNewlineIndex(data)) !== -1) {
yield data.slice(0, patternIndex);
data = data.slice(patternIndex);
}
}
if (data.length > 0) {
yield data;
}
}
class SSEDecoder {
constructor() {
this.event = null;
this.data = [];
this.chunks = [];
}
decode(line) {
if (line.endsWith('\r')) {
line = line.substring(0, line.length - 1);
}
if (!line) {
// empty line and we didn't previously encounter any messages
if (!this.event && !this.data.length)
return null;
const sse = {
event: this.event,
data: this.data.join('\n'),
raw: this.chunks,
};
this.event = null;
this.data = [];
this.chunks = [];
return sse;
}
this.chunks.push(line);
if (line.startsWith(':')) {
return null;
}
let [fieldname, _, value] = partition(line, ':');
if (value.startsWith(' ')) {
value = value.substring(1);
}
if (fieldname === 'event') {
this.event = value;
}
else if (fieldname === 'data') {
this.data.push(value);
}
return null;
}
}
function partition(str, delimiter) {
const index = str.indexOf(delimiter);
if (index !== -1) {
return [str.substring(0, index), delimiter, str.substring(index + delimiter.length)];
}
return [str, '', ''];
}
//# sourceMappingURL=streaming.mjs.map

View File

@@ -1,2 +0,0 @@
export { toFile } from "../internal/to-file.mjs";
//# sourceMappingURL=uploads.mjs.map

View File

@@ -1,2 +0,0 @@
export * from "./core/error.mjs";
//# sourceMappingURL=error.mjs.map

View File

@@ -1,8 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export { Anthropic as default } from "./client.mjs";
export { toFile } from "./core/uploads.mjs";
export { APIPromise } from "./core/api-promise.mjs";
export { BaseAnthropic, Anthropic, HUMAN_PROMPT, AI_PROMPT } from "./client.mjs";
export { PagePromise } from "./core/pagination.mjs";
export { AnthropicError, APIError, APIConnectionError, APIConnectionTimeoutError, APIUserAbortError, NotFoundError, ConflictError, RateLimitError, BadRequestError, AuthenticationError, InternalServerError, PermissionDeniedError, UnprocessableEntityError, } from "./core/error.mjs";
//# sourceMappingURL=index.mjs.map

View File

@@ -1,15 +0,0 @@
// File containing shared constants
/**
* Model-specific timeout constraints for non-streaming requests
*/
export const MODEL_NONSTREAMING_TOKENS = {
'claude-opus-4-20250514': 8192,
'claude-opus-4-0': 8192,
'claude-4-opus-20250514': 8192,
'anthropic.claude-opus-4-20250514-v1:0': 8192,
'claude-opus-4@20250514': 8192,
'claude-opus-4-1-20250805': 8192,
'anthropic.claude-opus-4-1-20250805-v1:0': 8192,
'claude-opus-4-1@20250805': 8192,
};
//# sourceMappingURL=constants.mjs.map

View File

@@ -1,35 +0,0 @@
import { AnthropicError } from "../../core/error.mjs";
import { ReadableStreamToAsyncIterable } from "../shims.mjs";
import { LineDecoder } from "./line.mjs";
export class JSONLDecoder {
constructor(iterator, controller) {
this.iterator = iterator;
this.controller = controller;
}
async *decoder() {
const lineDecoder = new LineDecoder();
for await (const chunk of this.iterator) {
for (const line of lineDecoder.decode(chunk)) {
yield JSON.parse(line);
}
}
for (const line of lineDecoder.flush()) {
yield JSON.parse(line);
}
}
[Symbol.asyncIterator]() {
return this.decoder();
}
static fromResponse(response, controller) {
if (!response.body) {
controller.abort();
if (typeof globalThis.navigator !== 'undefined' &&
globalThis.navigator.product === 'ReactNative') {
throw new AnthropicError(`The default react-native fetch implementation does not support streaming. Please use expo/fetch: https://docs.expo.dev/versions/latest/sdk/expo/#expofetch-api`);
}
throw new AnthropicError(`Attempted to iterate over a response with no body`);
}
return new JSONLDecoder(ReadableStreamToAsyncIterable(response.body), controller);
}
}
//# sourceMappingURL=jsonl.mjs.map

View File

@@ -1,108 +0,0 @@
var _LineDecoder_buffer, _LineDecoder_carriageReturnIndex;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../tslib.mjs";
import { concatBytes, decodeUTF8, encodeUTF8 } from "../utils/bytes.mjs";
/**
* A re-implementation of httpx's `LineDecoder` in Python that handles incrementally
* reading lines from text.
*
* https://github.com/encode/httpx/blob/920333ea98118e9cf617f246905d7b202510941c/httpx/_decoders.py#L258
*/
export class LineDecoder {
constructor() {
_LineDecoder_buffer.set(this, void 0);
_LineDecoder_carriageReturnIndex.set(this, void 0);
__classPrivateFieldSet(this, _LineDecoder_buffer, new Uint8Array(), "f");
__classPrivateFieldSet(this, _LineDecoder_carriageReturnIndex, null, "f");
}
decode(chunk) {
if (chunk == null) {
return [];
}
const binaryChunk = chunk instanceof ArrayBuffer ? new Uint8Array(chunk)
: typeof chunk === 'string' ? encodeUTF8(chunk)
: chunk;
__classPrivateFieldSet(this, _LineDecoder_buffer, concatBytes([__classPrivateFieldGet(this, _LineDecoder_buffer, "f"), binaryChunk]), "f");
const lines = [];
let patternIndex;
while ((patternIndex = findNewlineIndex(__classPrivateFieldGet(this, _LineDecoder_buffer, "f"), __classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f"))) != null) {
if (patternIndex.carriage && __classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f") == null) {
// skip until we either get a corresponding `\n`, a new `\r` or nothing
__classPrivateFieldSet(this, _LineDecoder_carriageReturnIndex, patternIndex.index, "f");
continue;
}
// we got double \r or \rtext\n
if (__classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f") != null &&
(patternIndex.index !== __classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f") + 1 || patternIndex.carriage)) {
lines.push(decodeUTF8(__classPrivateFieldGet(this, _LineDecoder_buffer, "f").subarray(0, __classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f") - 1)));
__classPrivateFieldSet(this, _LineDecoder_buffer, __classPrivateFieldGet(this, _LineDecoder_buffer, "f").subarray(__classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f")), "f");
__classPrivateFieldSet(this, _LineDecoder_carriageReturnIndex, null, "f");
continue;
}
const endIndex = __classPrivateFieldGet(this, _LineDecoder_carriageReturnIndex, "f") !== null ? patternIndex.preceding - 1 : patternIndex.preceding;
const line = decodeUTF8(__classPrivateFieldGet(this, _LineDecoder_buffer, "f").subarray(0, endIndex));
lines.push(line);
__classPrivateFieldSet(this, _LineDecoder_buffer, __classPrivateFieldGet(this, _LineDecoder_buffer, "f").subarray(patternIndex.index), "f");
__classPrivateFieldSet(this, _LineDecoder_carriageReturnIndex, null, "f");
}
return lines;
}
flush() {
if (!__classPrivateFieldGet(this, _LineDecoder_buffer, "f").length) {
return [];
}
return this.decode('\n');
}
}
_LineDecoder_buffer = new WeakMap(), _LineDecoder_carriageReturnIndex = new WeakMap();
// prettier-ignore
LineDecoder.NEWLINE_CHARS = new Set(['\n', '\r']);
LineDecoder.NEWLINE_REGEXP = /\r\n|[\n\r]/g;
/**
* This function searches the buffer for the end patterns, (\r or \n)
* and returns an object with the index preceding the matched newline and the
* index after the newline char. `null` is returned if no new line is found.
*
* ```ts
* findNewLineIndex('abc\ndef') -> { preceding: 2, index: 3 }
* ```
*/
function findNewlineIndex(buffer, startIndex) {
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = startIndex ?? 0; i < buffer.length; i++) {
if (buffer[i] === newline) {
return { preceding: i, index: i + 1, carriage: false };
}
if (buffer[i] === carriage) {
return { preceding: i, index: i + 1, carriage: true };
}
}
return null;
}
export function findDoubleNewlineIndex(buffer) {
// This function searches the buffer for the end patterns (\r\r, \n\n, \r\n\r\n)
// and returns the index right after the first occurrence of any pattern,
// or -1 if none of the patterns are found.
const newline = 0x0a; // \n
const carriage = 0x0d; // \r
for (let i = 0; i < buffer.length - 1; i++) {
if (buffer[i] === newline && buffer[i + 1] === newline) {
// \n\n
return i + 2;
}
if (buffer[i] === carriage && buffer[i + 1] === carriage) {
// \r\r
return i + 2;
}
if (buffer[i] === carriage &&
buffer[i + 1] === newline &&
i + 3 < buffer.length &&
buffer[i + 2] === carriage &&
buffer[i + 3] === newline) {
// \r\n\r\n
return i + 4;
}
}
return -1;
}
//# sourceMappingURL=line.mjs.map

View File

@@ -1,157 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { VERSION } from "../version.mjs";
export const isRunningInBrowser = () => {
return (
// @ts-ignore
typeof window !== 'undefined' &&
// @ts-ignore
typeof window.document !== 'undefined' &&
// @ts-ignore
typeof navigator !== 'undefined');
};
/**
* Note this does not detect 'browser'; for that, use getBrowserInfo().
*/
function getDetectedPlatform() {
if (typeof Deno !== 'undefined' && Deno.build != null) {
return 'deno';
}
if (typeof EdgeRuntime !== 'undefined') {
return 'edge';
}
if (Object.prototype.toString.call(typeof globalThis.process !== 'undefined' ? globalThis.process : 0) === '[object process]') {
return 'node';
}
return 'unknown';
}
const getPlatformProperties = () => {
const detectedPlatform = getDetectedPlatform();
if (detectedPlatform === 'deno') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform(Deno.build.os),
'X-Stainless-Arch': normalizeArch(Deno.build.arch),
'X-Stainless-Runtime': 'deno',
'X-Stainless-Runtime-Version': typeof Deno.version === 'string' ? Deno.version : Deno.version?.deno ?? 'unknown',
};
}
if (typeof EdgeRuntime !== 'undefined') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': `other:${EdgeRuntime}`,
'X-Stainless-Runtime': 'edge',
'X-Stainless-Runtime-Version': globalThis.process.version,
};
}
// Check if Node.js
if (detectedPlatform === 'node') {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': normalizePlatform(globalThis.process.platform ?? 'unknown'),
'X-Stainless-Arch': normalizeArch(globalThis.process.arch ?? 'unknown'),
'X-Stainless-Runtime': 'node',
'X-Stainless-Runtime-Version': globalThis.process.version ?? 'unknown',
};
}
const browserInfo = getBrowserInfo();
if (browserInfo) {
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': `browser:${browserInfo.browser}`,
'X-Stainless-Runtime-Version': browserInfo.version,
};
}
// TODO add support for Cloudflare workers, etc.
return {
'X-Stainless-Lang': 'js',
'X-Stainless-Package-Version': VERSION,
'X-Stainless-OS': 'Unknown',
'X-Stainless-Arch': 'unknown',
'X-Stainless-Runtime': 'unknown',
'X-Stainless-Runtime-Version': 'unknown',
};
};
// Note: modified from https://github.com/JS-DevTools/host-environment/blob/b1ab79ecde37db5d6e163c050e54fe7d287d7c92/src/isomorphic.browser.ts
function getBrowserInfo() {
if (typeof navigator === 'undefined' || !navigator) {
return null;
}
// NOTE: The order matters here!
const browserPatterns = [
{ key: 'edge', pattern: /Edge(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie', pattern: /MSIE(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'ie', pattern: /Trident(?:.*rv\:(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'chrome', pattern: /Chrome(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'firefox', pattern: /Firefox(?:\W+(\d+)\.(\d+)(?:\.(\d+))?)?/ },
{ key: 'safari', pattern: /(?:Version\W+(\d+)\.(\d+)(?:\.(\d+))?)?(?:\W+Mobile\S*)?\W+Safari/ },
];
// Find the FIRST matching browser
for (const { key, pattern } of browserPatterns) {
const match = pattern.exec(navigator.userAgent);
if (match) {
const major = match[1] || 0;
const minor = match[2] || 0;
const patch = match[3] || 0;
return { browser: key, version: `${major}.${minor}.${patch}` };
}
}
return null;
}
const normalizeArch = (arch) => {
// Node docs:
// - https://nodejs.org/api/process.html#processarch
// Deno docs:
// - https://doc.deno.land/deno/stable/~/Deno.build
if (arch === 'x32')
return 'x32';
if (arch === 'x86_64' || arch === 'x64')
return 'x64';
if (arch === 'arm')
return 'arm';
if (arch === 'aarch64' || arch === 'arm64')
return 'arm64';
if (arch)
return `other:${arch}`;
return 'unknown';
};
const normalizePlatform = (platform) => {
// Node platforms:
// - https://nodejs.org/api/process.html#processplatform
// Deno platforms:
// - https://doc.deno.land/deno/stable/~/Deno.build
// - https://github.com/denoland/deno/issues/14799
platform = platform.toLowerCase();
// NOTE: this iOS check is untested and may not work
// Node does not work natively on IOS, there is a fork at
// https://github.com/nodejs-mobile/nodejs-mobile
// however it is unknown at the time of writing how to detect if it is running
if (platform.includes('ios'))
return 'iOS';
if (platform === 'android')
return 'Android';
if (platform === 'darwin')
return 'MacOS';
if (platform === 'win32')
return 'Windows';
if (platform === 'freebsd')
return 'FreeBSD';
if (platform === 'openbsd')
return 'OpenBSD';
if (platform === 'linux')
return 'Linux';
if (platform)
return `Other:${platform}`;
return 'Unknown';
};
let _platformHeaders;
export const getPlatformHeaders = () => {
return (_platformHeaders ?? (_platformHeaders = getPlatformProperties()));
};
//# sourceMappingURL=detect-platform.mjs.map

View File

@@ -1,36 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export function isAbortError(err) {
return (typeof err === 'object' &&
err !== null &&
// Spec-compliant fetch implementations
(('name' in err && err.name === 'AbortError') ||
// Expo fetch
('message' in err && String(err.message).includes('FetchRequestCanceledException'))));
}
export const castToError = (err) => {
if (err instanceof Error)
return err;
if (typeof err === 'object' && err !== null) {
try {
if (Object.prototype.toString.call(err) === '[object Error]') {
// @ts-ignore - not all envs have native support for cause yet
const error = new Error(err.message, err.cause ? { cause: err.cause } : {});
if (err.stack)
error.stack = err.stack;
// @ts-ignore - not all envs have native support for cause yet
if (err.cause && !error.cause)
error.cause = err.cause;
if (err.name)
error.name = err.name;
return error;
}
}
catch { }
try {
return new Error(JSON.stringify(err));
}
catch { }
}
return new Error(err);
};
//# sourceMappingURL=errors.mjs.map

View File

@@ -1,74 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from "./utils/values.mjs";
const brand_privateNullableHeaders = Symbol.for('brand.privateNullableHeaders');
function* iterateHeaders(headers) {
if (!headers)
return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter;
if (headers instanceof Headers) {
iter = headers.entries();
}
else if (isReadonlyArray(headers)) {
iter = headers;
}
else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string')
throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined)
continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders) => {
const targetHeaders = new Headers();
const nullHeaders = new Set();
for (const headers of newHeaders) {
const seenHeaders = new Set();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
}
else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers) => {
for (const _ of iterateHeaders(headers))
return false;
return true;
};
//# sourceMappingURL=headers.mjs.map

View File

@@ -1,56 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { Stream } from "../core/streaming.mjs";
import { formatRequestDetails, loggerFor } from "./utils/log.mjs";
export async function defaultParseResponse(client, props) {
const { response, requestLogID, retryOfRequestLogID, startTime } = props;
const body = await (async () => {
if (props.options.stream) {
loggerFor(client).debug('response', response.status, response.url, response.headers, response.body);
// Note: there is an invariant here that isn't represented in the type system
// that if you set `stream: true` the response type must also be `Stream<T>`
if (props.options.__streamClass) {
return props.options.__streamClass.fromSSEResponse(response, props.controller);
}
return Stream.fromSSEResponse(response, props.controller);
}
// fetch refuses to read the body when the status code is 204.
if (response.status === 204) {
return null;
}
if (props.options.__binaryResponse) {
return response;
}
const contentType = response.headers.get('content-type');
const mediaType = contentType?.split(';')[0]?.trim();
const isJSON = mediaType?.includes('application/json') || mediaType?.endsWith('+json');
if (isJSON) {
const contentLength = response.headers.get('content-length');
if (contentLength === '0') {
// if there is no content we can't do anything
return undefined;
}
const json = await response.json();
return addRequestID(json, response);
}
const text = await response.text();
return text;
})();
loggerFor(client).debug(`[${requestLogID}] response parsed`, formatRequestDetails({
retryOfRequestLogID,
url: response.url,
status: response.status,
body,
durationMs: Date.now() - startTime,
}));
return body;
}
export function addRequestID(value, response) {
if (!value || typeof value !== 'object' || Array.isArray(value)) {
return value;
}
return Object.defineProperty(value, '_request_id', {
value: response.headers.get('request-id'),
enumerable: false,
});
}
//# sourceMappingURL=parse.mjs.map

View File

@@ -1,10 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export const FallbackEncoder = ({ headers, body }) => {
return {
bodyHeaders: {
'content-type': 'application/json',
},
body: JSON.stringify(body),
};
};
//# sourceMappingURL=request-options.mjs.map

View File

@@ -1,85 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export function getDefaultFetch() {
if (typeof fetch !== 'undefined') {
return fetch;
}
throw new Error('`fetch` is not defined as a global; Either pass `fetch` to the client, `new Anthropic({ fetch })` or polyfill the global, `globalThis.fetch = fetch`');
}
export function makeReadableStream(...args) {
const ReadableStream = globalThis.ReadableStream;
if (typeof ReadableStream === 'undefined') {
// Note: All of the platforms / runtimes we officially support already define
// `ReadableStream` as a global, so this should only ever be hit on unsupported runtimes.
throw new Error('`ReadableStream` is not defined as a global; You will need to polyfill it, `globalThis.ReadableStream = ReadableStream`');
}
return new ReadableStream(...args);
}
export function ReadableStreamFrom(iterable) {
let iter = Symbol.asyncIterator in iterable ? iterable[Symbol.asyncIterator]() : iterable[Symbol.iterator]();
return makeReadableStream({
start() { },
async pull(controller) {
const { done, value } = await iter.next();
if (done) {
controller.close();
}
else {
controller.enqueue(value);
}
},
async cancel() {
await iter.return?.();
},
});
}
/**
* Most browsers don't yet have async iterable support for ReadableStream,
* and Node has a very different way of reading bytes from its "ReadableStream".
*
* This polyfill was pulled from https://github.com/MattiasBuelens/web-streams-polyfill/pull/122#issuecomment-1627354490
*/
export function ReadableStreamToAsyncIterable(stream) {
if (stream[Symbol.asyncIterator])
return stream;
const reader = stream.getReader();
return {
async next() {
try {
const result = await reader.read();
if (result?.done)
reader.releaseLock(); // release lock when stream becomes closed
return result;
}
catch (e) {
reader.releaseLock(); // release lock when stream becomes errored
throw e;
}
},
async return() {
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
return { done: true, value: undefined };
},
[Symbol.asyncIterator]() {
return this;
},
};
}
/**
* Cancels a ReadableStream we don't need to consume.
* See https://undici.nodejs.org/#/?id=garbage-collection
*/
export async function CancelReadableStream(stream) {
if (stream === null || typeof stream !== 'object')
return;
if (stream[Symbol.asyncIterator]) {
await stream[Symbol.asyncIterator]().return?.();
return;
}
const reader = stream.getReader();
const cancelPromise = reader.cancel();
reader.releaseLock();
await cancelPromise;
}
//# sourceMappingURL=shims.mjs.map

View File

@@ -1,93 +0,0 @@
import { getName, makeFile, isAsyncIterable } from "./uploads.mjs";
import { checkFileSupport } from "./uploads.mjs";
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isBlobLike = (value) => value != null &&
typeof value === 'object' &&
typeof value.size === 'number' &&
typeof value.type === 'string' &&
typeof value.text === 'function' &&
typeof value.slice === 'function' &&
typeof value.arrayBuffer === 'function';
/**
* This check adds the arrayBuffer() method type because it is available and used at runtime
*/
const isFileLike = (value) => value != null &&
typeof value === 'object' &&
typeof value.name === 'string' &&
typeof value.lastModified === 'number' &&
isBlobLike(value);
const isResponseLike = (value) => value != null &&
typeof value === 'object' &&
typeof value.url === 'string' &&
typeof value.blob === 'function';
/**
* Helper for creating a {@link File} to pass to an SDK upload method from a variety of different data formats
* @param value the raw content of the file. Can be an {@link Uploadable}, BlobLikePart, or AsyncIterable of BlobLikeParts
* @param {string=} name the name of the file. If omitted, toFile will try to determine a file name from bits if possible
* @param {Object=} options additional properties
* @param {string=} options.type the MIME type of the content
* @param {number=} options.lastModified the last modified timestamp
* @returns a {@link File} with the given properties
*/
export async function toFile(value, name, options) {
checkFileSupport();
// If it's a promise, resolve it.
value = await value;
name || (name = getName(value, true));
// If we've been given a `File` we don't need to do anything if the name / options
// have not been customised.
if (isFileLike(value)) {
if (value instanceof File && name == null && options == null) {
return value;
}
return makeFile([await value.arrayBuffer()], name ?? value.name, {
type: value.type,
lastModified: value.lastModified,
...options,
});
}
if (isResponseLike(value)) {
const blob = await value.blob();
name || (name = new URL(value.url).pathname.split(/[\\/]/).pop());
return makeFile(await getBytes(blob), name, options);
}
const parts = await getBytes(value);
if (!options?.type) {
const type = parts.find((part) => typeof part === 'object' && 'type' in part && part.type);
if (typeof type === 'string') {
options = { ...options, type };
}
}
return makeFile(parts, name, options);
}
async function getBytes(value) {
let parts = [];
if (typeof value === 'string' ||
ArrayBuffer.isView(value) || // includes Uint8Array, Buffer, etc.
value instanceof ArrayBuffer) {
parts.push(value);
}
else if (isBlobLike(value)) {
parts.push(value instanceof Blob ? value : await value.arrayBuffer());
}
else if (isAsyncIterable(value) // includes Readable, ReadableStream, etc.
) {
for await (const chunk of value) {
parts.push(...(await getBytes(chunk))); // TODO, consider validating?
}
}
else {
const constructor = value?.constructor?.name;
throw new Error(`Unexpected data type: ${typeof value}${constructor ? `; constructor: ${constructor}` : ''}${propsForError(value)}`);
}
return parts;
}
function propsForError(value) {
if (typeof value !== 'object' || value === null)
return '';
const props = Object.getOwnPropertyNames(value);
return `; props: [${props.map((p) => `"${p}"`).join(', ')}]`;
}
//# sourceMappingURL=to-file.mjs.map

View File

@@ -1,17 +0,0 @@
function __classPrivateFieldSet(receiver, state, value, kind, f) {
if (kind === "m")
throw new TypeError("Private method is not writable");
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a setter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot write private member to an object whose class did not declare it");
return kind === "a" ? f.call(receiver, value) : f ? (f.value = value) : state.set(receiver, value), value;
}
function __classPrivateFieldGet(receiver, state, kind, f) {
if (kind === "a" && !f)
throw new TypeError("Private accessor was defined without a getter");
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver))
throw new TypeError("Cannot read private member from an object whose class did not declare it");
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
}
export { __classPrivateFieldSet, __classPrivateFieldGet };

View File

@@ -1,135 +0,0 @@
import { ReadableStreamFrom } from "./shims.mjs";
export const checkFileSupport = () => {
if (typeof File === 'undefined') {
const { process } = globalThis;
const isOldNode = typeof process?.versions?.node === 'string' && parseInt(process.versions.node.split('.')) < 20;
throw new Error('`File` is not defined as a global, which is required for file uploads.' +
(isOldNode ?
" Update to Node 20 LTS or newer, or set `globalThis.File` to `import('node:buffer').File`."
: ''));
}
};
/**
* Construct a `File` instance. This is used to ensure a helpful error is thrown
* for environments that don't define a global `File` yet.
*/
export function makeFile(fileBits, fileName, options) {
checkFileSupport();
return new File(fileBits, fileName ?? 'unknown_file', options);
}
export function getName(value, stripPath) {
const val = (typeof value === 'object' &&
value !== null &&
(('name' in value && value.name && String(value.name)) ||
('url' in value && value.url && String(value.url)) ||
('filename' in value && value.filename && String(value.filename)) ||
('path' in value && value.path && String(value.path)))) ||
'';
return stripPath ? val.split(/[\\/]/).pop() || undefined : val;
}
export const isAsyncIterable = (value) => value != null && typeof value === 'object' && typeof value[Symbol.asyncIterator] === 'function';
/**
* Returns a multipart/form-data request if any part of the given request body contains a File / Blob value.
* Otherwise returns the request as is.
*/
export const maybeMultipartFormRequestOptions = async (opts, fetch) => {
if (!hasUploadableValue(opts.body))
return opts;
return { ...opts, body: await createForm(opts.body, fetch) };
};
export const multipartFormRequestOptions = async (opts, fetch, stripFilenames = true) => {
return { ...opts, body: await createForm(opts.body, fetch, stripFilenames) };
};
const supportsFormDataMap = /* @__PURE__ */ new WeakMap();
/**
* node-fetch doesn't support the global FormData object in recent node versions. Instead of sending
* properly-encoded form data, it just stringifies the object, resulting in a request body of "[object FormData]".
* This function detects if the fetch function provided supports the global FormData object to avoid
* confusing error messages later on.
*/
function supportsFormData(fetchObject) {
const fetch = typeof fetchObject === 'function' ? fetchObject : fetchObject.fetch;
const cached = supportsFormDataMap.get(fetch);
if (cached)
return cached;
const promise = (async () => {
try {
const FetchResponse = ('Response' in fetch ?
fetch.Response
: (await fetch('data:,')).constructor);
const data = new FormData();
if (data.toString() === (await new FetchResponse(data).text())) {
return false;
}
return true;
}
catch {
// avoid false negatives
return true;
}
})();
supportsFormDataMap.set(fetch, promise);
return promise;
}
export const createForm = async (body, fetch, stripFilenames = true) => {
if (!(await supportsFormData(fetch))) {
throw new TypeError('The provided fetch function does not support file uploads with the current global FormData class.');
}
const form = new FormData();
await Promise.all(Object.entries(body || {}).map(([key, value]) => addFormValue(form, key, value, stripFilenames)));
return form;
};
// We check for Blob not File because Bun.File doesn't inherit from File,
// but they both inherit from Blob and have a `name` property at runtime.
const isNamedBlob = (value) => value instanceof Blob && 'name' in value;
const isUploadable = (value) => typeof value === 'object' &&
value !== null &&
(value instanceof Response || isAsyncIterable(value) || isNamedBlob(value));
const hasUploadableValue = (value) => {
if (isUploadable(value))
return true;
if (Array.isArray(value))
return value.some(hasUploadableValue);
if (value && typeof value === 'object') {
for (const k in value) {
if (hasUploadableValue(value[k]))
return true;
}
}
return false;
};
const addFormValue = async (form, key, value, stripFilenames) => {
if (value === undefined)
return;
if (value == null) {
throw new TypeError(`Received null for "${key}"; to pass null in FormData, you must use the string 'null'`);
}
// TODO: make nested formats configurable
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
form.append(key, String(value));
}
else if (value instanceof Response) {
let options = {};
const contentType = value.headers.get('Content-Type');
if (contentType) {
options = { type: contentType };
}
form.append(key, makeFile([await value.blob()], getName(value, stripFilenames), options));
}
else if (isAsyncIterable(value)) {
form.append(key, makeFile([await new Response(ReadableStreamFrom(value)).blob()], getName(value, stripFilenames)));
}
else if (isNamedBlob(value)) {
form.append(key, makeFile([value], getName(value, stripFilenames), { type: value.type }));
}
else if (Array.isArray(value)) {
await Promise.all(value.map((entry) => addFormValue(form, key + '[]', entry, stripFilenames)));
}
else if (typeof value === 'object') {
await Promise.all(Object.entries(value).map(([name, prop]) => addFormValue(form, `${key}[${name}]`, prop, stripFilenames)));
}
else {
throw new TypeError(`Invalid value given to form, expected a string, number, boolean, object, Array, File or Blob but got ${value} instead`);
}
};
//# sourceMappingURL=uploads.mjs.map

View File

@@ -1,26 +0,0 @@
export function concatBytes(buffers) {
let length = 0;
for (const buffer of buffers) {
length += buffer.length;
}
const output = new Uint8Array(length);
let index = 0;
for (const buffer of buffers) {
output.set(buffer, index);
index += buffer.length;
}
return output;
}
let encodeUTF8_;
export function encodeUTF8(str) {
let encoder;
return (encodeUTF8_ ??
((encoder = new globalThis.TextEncoder()), (encodeUTF8_ = encoder.encode.bind(encoder))))(str);
}
let decodeUTF8_;
export function decodeUTF8(bytes) {
let decoder;
return (decodeUTF8_ ??
((decoder = new globalThis.TextDecoder()), (decodeUTF8_ = decoder.decode.bind(decoder))))(bytes);
}
//# sourceMappingURL=bytes.mjs.map

View File

@@ -1,18 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env) => {
if (typeof globalThis.process !== 'undefined') {
return globalThis.process.env?.[env]?.trim() ?? undefined;
}
if (typeof globalThis.Deno !== 'undefined') {
return globalThis.Deno.env?.get?.(env)?.trim();
}
return undefined;
};
//# sourceMappingURL=env.mjs.map

View File

@@ -1,80 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { hasOwn } from "./values.mjs";
const levelNumbers = {
off: 0,
error: 200,
warn: 300,
info: 400,
debug: 500,
};
export const parseLogLevel = (maybeLevel, sourceName, client) => {
if (!maybeLevel) {
return undefined;
}
if (hasOwn(levelNumbers, maybeLevel)) {
return maybeLevel;
}
loggerFor(client).warn(`${sourceName} was set to ${JSON.stringify(maybeLevel)}, expected one of ${JSON.stringify(Object.keys(levelNumbers))}`);
return undefined;
};
function noop() { }
function makeLogFn(fnLevel, logger, logLevel) {
if (!logger || levelNumbers[fnLevel] > levelNumbers[logLevel]) {
return noop;
}
else {
// Don't wrap logger functions, we want the stacktrace intact!
return logger[fnLevel].bind(logger);
}
}
const noopLogger = {
error: noop,
warn: noop,
info: noop,
debug: noop,
};
let cachedLoggers = /* @__PURE__ */ new WeakMap();
export function loggerFor(client) {
const logger = client.logger;
const logLevel = client.logLevel ?? 'off';
if (!logger) {
return noopLogger;
}
const cachedLogger = cachedLoggers.get(logger);
if (cachedLogger && cachedLogger[0] === logLevel) {
return cachedLogger[1];
}
const levelLogger = {
error: makeLogFn('error', logger, logLevel),
warn: makeLogFn('warn', logger, logLevel),
info: makeLogFn('info', logger, logLevel),
debug: makeLogFn('debug', logger, logLevel),
};
cachedLoggers.set(logger, [logLevel, levelLogger]);
return levelLogger;
}
export const formatRequestDetails = (details) => {
if (details.options) {
details.options = { ...details.options };
delete details.options['headers']; // redundant + leaks internals
}
if (details.headers) {
details.headers = Object.fromEntries((details.headers instanceof Headers ? [...details.headers] : Object.entries(details.headers)).map(([name, value]) => [
name,
(name.toLowerCase() === 'x-api-key' ||
name.toLowerCase() === 'authorization' ||
name.toLowerCase() === 'cookie' ||
name.toLowerCase() === 'set-cookie') ?
'***'
: value,
]));
}
if ('retryOfRequestLogID' in details) {
if (details.retryOfRequestLogID) {
details.retryOf = details.retryOfRequestLogID;
}
delete details.retryOfRequestLogID;
}
return details;
};
//# sourceMappingURL=log.mjs.map

View File

@@ -1,74 +0,0 @@
import { AnthropicError } from "../../core/error.mjs";
/**
* Percent-encode everything that isn't safe to have in a path without encoding safe chars.
*
* Taken from https://datatracker.ietf.org/doc/html/rfc3986#section-3.3:
* > unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
* > sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
* > pchar = unreserved / pct-encoded / sub-delims / ":" / "@"
*/
export function encodeURIPath(str) {
return str.replace(/[^A-Za-z0-9\-._~!$&'()*+,;=:@]+/g, encodeURIComponent);
}
const EMPTY = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.create(null));
export const createPathTagFunction = (pathEncoder = encodeURIPath) => function path(statics, ...params) {
// If there are no params, no processing is needed.
if (statics.length === 1)
return statics[0];
let postPath = false;
const invalidSegments = [];
const path = statics.reduce((previousValue, currentValue, index) => {
if (/[?#]/.test(currentValue)) {
postPath = true;
}
const value = params[index];
let encoded = (postPath ? encodeURIComponent : pathEncoder)('' + value);
if (index !== params.length &&
(value == null ||
(typeof value === 'object' &&
// handle values from other realms
value.toString ===
Object.getPrototypeOf(Object.getPrototypeOf(value.hasOwnProperty ?? EMPTY) ?? EMPTY)
?.toString))) {
encoded = value + '';
invalidSegments.push({
start: previousValue.length + currentValue.length,
length: encoded.length,
error: `Value of type ${Object.prototype.toString
.call(value)
.slice(8, -1)} is not a valid path parameter`,
});
}
return previousValue + currentValue + (index === params.length ? '' : encoded);
}, '');
const pathOnly = path.split(/[?#]/, 1)[0];
const invalidSegmentPattern = /(?<=^|\/)(?:\.|%2e){1,2}(?=\/|$)/gi;
let match;
// Find all invalid segments
while ((match = invalidSegmentPattern.exec(pathOnly)) !== null) {
invalidSegments.push({
start: match.index,
length: match[0].length,
error: `Value "${match[0]}" can\'t be safely passed as a path parameter`,
});
}
invalidSegments.sort((a, b) => a.start - b.start);
if (invalidSegments.length > 0) {
let lastEnd = 0;
const underline = invalidSegments.reduce((acc, segment) => {
const spaces = ' '.repeat(segment.start - lastEnd);
const arrows = '^'.repeat(segment.length);
lastEnd = segment.start + segment.length;
return acc + spaces + arrows;
}, '');
throw new AnthropicError(`Path parameters result in path with invalid segments:\n${invalidSegments
.map((e) => e.error)
.join('\n')}\n${path}\n${underline}`);
}
return path;
};
/**
* URI-encodes path params and ensures no unsafe /./ or /../ path segments are introduced.
*/
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);
//# sourceMappingURL=path.mjs.map

View File

@@ -1,3 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
//# sourceMappingURL=sleep.mjs.map

View File

@@ -1,15 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* https://stackoverflow.com/a/2117523
*/
export let uuid4 = function () {
const { crypto } = globalThis;
if (crypto?.randomUUID) {
uuid4 = crypto.randomUUID.bind(crypto);
return crypto.randomUUID();
}
const u8 = new Uint8Array(1);
const randomByte = crypto ? () => crypto.getRandomValues(u8)[0] : () => (Math.random() * 0xff) & 0xff;
return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, (c) => (+c ^ (randomByte() & (15 >> (+c / 4)))).toString(16));
};
//# sourceMappingURL=uuid.mjs.map

View File

@@ -1,100 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../core/error.mjs";
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url) => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val) => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray;
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x) {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj) {
if (!obj)
return true;
for (const _k in obj)
return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj) {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = (value) => {
if (value == null) {
throw new AnthropicError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name, n) => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new AnthropicError(`${name} must be an integer`);
}
if (n < 0) {
throw new AnthropicError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value) => {
if (typeof value === 'number')
return Math.round(value);
if (typeof value === 'string')
return parseInt(value, 10);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value) => {
if (typeof value === 'number')
return value;
if (typeof value === 'string')
return parseFloat(value);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value) => {
if (typeof value === 'boolean')
return value;
if (typeof value === 'string')
return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value) => {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value) => {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value) => {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text) => {
try {
return JSON.parse(text);
}
catch (err) {
return undefined;
}
};
// Gets a value from an object, deletes the key, and returns the value (or undefined if not found)
export const pop = (obj, key) => {
const value = obj[key];
delete obj[key];
return value;
};
//# sourceMappingURL=values.mjs.map

View File

@@ -1,616 +0,0 @@
var _BetaMessageStream_instances, _BetaMessageStream_currentMessageSnapshot, _BetaMessageStream_params, _BetaMessageStream_connectedPromise, _BetaMessageStream_resolveConnectedPromise, _BetaMessageStream_rejectConnectedPromise, _BetaMessageStream_endPromise, _BetaMessageStream_resolveEndPromise, _BetaMessageStream_rejectEndPromise, _BetaMessageStream_listeners, _BetaMessageStream_ended, _BetaMessageStream_errored, _BetaMessageStream_aborted, _BetaMessageStream_catchingPromiseCreated, _BetaMessageStream_response, _BetaMessageStream_request_id, _BetaMessageStream_logger, _BetaMessageStream_getFinalMessage, _BetaMessageStream_getFinalText, _BetaMessageStream_handleError, _BetaMessageStream_beginRequest, _BetaMessageStream_addStreamEvent, _BetaMessageStream_endRequest, _BetaMessageStream_accumulateMessage;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../internal/tslib.mjs";
import { partialParse } from "../_vendor/partial-json-parser/parser.mjs";
import { AnthropicError, APIUserAbortError } from "../error.mjs";
import { isAbortError } from "../internal/errors.mjs";
import { Stream } from "../streaming.mjs";
import { maybeParseBetaMessage } from "./beta-parser.mjs";
const JSON_BUF_PROPERTY = '__json_buf';
function tracksToolInput(content) {
return content.type === 'tool_use' || content.type === 'server_tool_use' || content.type === 'mcp_tool_use';
}
export class BetaMessageStream {
constructor(params, opts) {
_BetaMessageStream_instances.add(this);
this.messages = [];
this.receivedMessages = [];
_BetaMessageStream_currentMessageSnapshot.set(this, void 0);
_BetaMessageStream_params.set(this, null);
this.controller = new AbortController();
_BetaMessageStream_connectedPromise.set(this, void 0);
_BetaMessageStream_resolveConnectedPromise.set(this, () => { });
_BetaMessageStream_rejectConnectedPromise.set(this, () => { });
_BetaMessageStream_endPromise.set(this, void 0);
_BetaMessageStream_resolveEndPromise.set(this, () => { });
_BetaMessageStream_rejectEndPromise.set(this, () => { });
_BetaMessageStream_listeners.set(this, {});
_BetaMessageStream_ended.set(this, false);
_BetaMessageStream_errored.set(this, false);
_BetaMessageStream_aborted.set(this, false);
_BetaMessageStream_catchingPromiseCreated.set(this, false);
_BetaMessageStream_response.set(this, void 0);
_BetaMessageStream_request_id.set(this, void 0);
_BetaMessageStream_logger.set(this, void 0);
_BetaMessageStream_handleError.set(this, (error) => {
__classPrivateFieldSet(this, _BetaMessageStream_errored, true, "f");
if (isAbortError(error)) {
error = new APIUserAbortError();
}
if (error instanceof APIUserAbortError) {
__classPrivateFieldSet(this, _BetaMessageStream_aborted, true, "f");
return this._emit('abort', error);
}
if (error instanceof AnthropicError) {
return this._emit('error', error);
}
if (error instanceof Error) {
const anthropicError = new AnthropicError(error.message);
// @ts-ignore
anthropicError.cause = error;
return this._emit('error', anthropicError);
}
return this._emit('error', new AnthropicError(String(error)));
});
__classPrivateFieldSet(this, _BetaMessageStream_connectedPromise, new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _BetaMessageStream_resolveConnectedPromise, resolve, "f");
__classPrivateFieldSet(this, _BetaMessageStream_rejectConnectedPromise, reject, "f");
}), "f");
__classPrivateFieldSet(this, _BetaMessageStream_endPromise, new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _BetaMessageStream_resolveEndPromise, resolve, "f");
__classPrivateFieldSet(this, _BetaMessageStream_rejectEndPromise, reject, "f");
}), "f");
// Don't let these promises cause unhandled rejection errors.
// we will manually cause an unhandled rejection error later
// if the user hasn't registered any error listener or called
// any promise-returning method.
__classPrivateFieldGet(this, _BetaMessageStream_connectedPromise, "f").catch(() => { });
__classPrivateFieldGet(this, _BetaMessageStream_endPromise, "f").catch(() => { });
__classPrivateFieldSet(this, _BetaMessageStream_params, params, "f");
__classPrivateFieldSet(this, _BetaMessageStream_logger, opts?.logger ?? console, "f");
}
get response() {
return __classPrivateFieldGet(this, _BetaMessageStream_response, "f");
}
get request_id() {
return __classPrivateFieldGet(this, _BetaMessageStream_request_id, "f");
}
/**
* Returns the `MessageStream` data, the raw `Response` instance and the ID of the request,
* returned vie the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* This is the same as the `APIPromise.withResponse()` method.
*
* This method will raise an error if you created the stream using `MessageStream.fromReadableStream`
* as no `Response` is available.
*/
async withResponse() {
__classPrivateFieldSet(this, _BetaMessageStream_catchingPromiseCreated, true, "f");
const response = await __classPrivateFieldGet(this, _BetaMessageStream_connectedPromise, "f");
if (!response) {
throw new Error('Could not resolve a `Response` object');
}
return {
data: this,
response,
request_id: response.headers.get('request-id'),
};
}
/**
* Intended for use on the frontend, consuming a stream produced with
* `.toReadableStream()` on the backend.
*
* Note that messages sent to the model do not appear in `.on('message')`
* in this context.
*/
static fromReadableStream(stream) {
const runner = new BetaMessageStream(null);
runner._run(() => runner._fromReadableStream(stream));
return runner;
}
static createMessage(messages, params, options, { logger } = {}) {
const runner = new BetaMessageStream(params, { logger });
for (const message of params.messages) {
runner._addMessageParam(message);
}
__classPrivateFieldSet(runner, _BetaMessageStream_params, { ...params, stream: true }, "f");
runner._run(() => runner._createMessage(messages, { ...params, stream: true }, { ...options, headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' } }));
return runner;
}
_run(executor) {
executor().then(() => {
this._emitFinal();
this._emit('end');
}, __classPrivateFieldGet(this, _BetaMessageStream_handleError, "f"));
}
_addMessageParam(message) {
this.messages.push(message);
}
_addMessage(message, emit = true) {
this.receivedMessages.push(message);
if (emit) {
this._emit('message', message);
}
}
async _createMessage(messages, params, options) {
const signal = options?.signal;
let abortHandler;
if (signal) {
if (signal.aborted)
this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_beginRequest).call(this);
const { response, data: stream } = await messages
.create({ ...params, stream: true }, { ...options, signal: this.controller.signal })
.withResponse();
this._connected(response);
for await (const event of stream) {
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_addStreamEvent).call(this, event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_endRequest).call(this);
}
finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
_connected(response) {
if (this.ended)
return;
__classPrivateFieldSet(this, _BetaMessageStream_response, response, "f");
__classPrivateFieldSet(this, _BetaMessageStream_request_id, response?.headers.get('request-id'), "f");
__classPrivateFieldGet(this, _BetaMessageStream_resolveConnectedPromise, "f").call(this, response);
this._emit('connect');
}
get ended() {
return __classPrivateFieldGet(this, _BetaMessageStream_ended, "f");
}
get errored() {
return __classPrivateFieldGet(this, _BetaMessageStream_errored, "f");
}
get aborted() {
return __classPrivateFieldGet(this, _BetaMessageStream_aborted, "f");
}
abort() {
this.controller.abort();
}
/**
* Adds the listener function to the end of the listeners array for the event.
* No checks are made to see if the listener has already been added. Multiple calls passing
* the same combination of event and listener will result in the listener being added, and
* called, multiple times.
* @returns this MessageStream, so that calls can be chained
*/
on(event, listener) {
const listeners = __classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event] || (__classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event] = []);
listeners.push({ listener });
return this;
}
/**
* Removes the specified listener from the listener array for the event.
* off() will remove, at most, one instance of a listener from the listener array. If any single
* listener has been added multiple times to the listener array for the specified event, then
* off() must be called multiple times to remove each instance.
* @returns this MessageStream, so that calls can be chained
*/
off(event, listener) {
const listeners = __classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event];
if (!listeners)
return this;
const index = listeners.findIndex((l) => l.listener === listener);
if (index >= 0)
listeners.splice(index, 1);
return this;
}
/**
* Adds a one-time listener function for the event. The next time the event is triggered,
* this listener is removed and then invoked.
* @returns this MessageStream, so that calls can be chained
*/
once(event, listener) {
const listeners = __classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event] || (__classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event] = []);
listeners.push({ listener, once: true });
return this;
}
/**
* This is similar to `.once()`, but returns a Promise that resolves the next time
* the event is triggered, instead of calling a listener callback.
* @returns a Promise that resolves the next time given event is triggered,
* or rejects if an error is emitted. (If you request the 'error' event,
* returns a promise that resolves with the error).
*
* Example:
*
* const message = await stream.emitted('message') // rejects if the stream errors
*/
emitted(event) {
return new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _BetaMessageStream_catchingPromiseCreated, true, "f");
if (event !== 'error')
this.once('error', reject);
this.once(event, resolve);
});
}
async done() {
__classPrivateFieldSet(this, _BetaMessageStream_catchingPromiseCreated, true, "f");
await __classPrivateFieldGet(this, _BetaMessageStream_endPromise, "f");
}
get currentMessage() {
return __classPrivateFieldGet(this, _BetaMessageStream_currentMessageSnapshot, "f");
}
/**
* @returns a promise that resolves with the the final assistant Message response,
* or rejects if an error occurred or the stream ended prematurely without producing a Message.
* If structured outputs were used, this will be a ParsedMessage with a `parsed` field.
*/
async finalMessage() {
await this.done();
return __classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_getFinalMessage).call(this);
}
/**
* @returns a promise that resolves with the the final assistant Message's text response, concatenated
* together if there are more than one text blocks.
* Rejects if an error occurred or the stream ended prematurely without producing a Message.
*/
async finalText() {
await this.done();
return __classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_getFinalText).call(this);
}
_emit(event, ...args) {
// make sure we don't emit any MessageStreamEvents after end
if (__classPrivateFieldGet(this, _BetaMessageStream_ended, "f"))
return;
if (event === 'end') {
__classPrivateFieldSet(this, _BetaMessageStream_ended, true, "f");
__classPrivateFieldGet(this, _BetaMessageStream_resolveEndPromise, "f").call(this);
}
const listeners = __classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event];
if (listeners) {
__classPrivateFieldGet(this, _BetaMessageStream_listeners, "f")[event] = listeners.filter((l) => !l.once);
listeners.forEach(({ listener }) => listener(...args));
}
if (event === 'abort') {
const error = args[0];
if (!__classPrivateFieldGet(this, _BetaMessageStream_catchingPromiseCreated, "f") && !listeners?.length) {
Promise.reject(error);
}
__classPrivateFieldGet(this, _BetaMessageStream_rejectConnectedPromise, "f").call(this, error);
__classPrivateFieldGet(this, _BetaMessageStream_rejectEndPromise, "f").call(this, error);
this._emit('end');
return;
}
if (event === 'error') {
// NOTE: _emit('error', error) should only be called from #handleError().
const error = args[0];
if (!__classPrivateFieldGet(this, _BetaMessageStream_catchingPromiseCreated, "f") && !listeners?.length) {
// Trigger an unhandled rejection if the user hasn't registered any error handlers.
// If you are seeing stack traces here, make sure to handle errors via either:
// - runner.on('error', () => ...)
// - await runner.done()
// - await runner.final...()
// - etc.
Promise.reject(error);
}
__classPrivateFieldGet(this, _BetaMessageStream_rejectConnectedPromise, "f").call(this, error);
__classPrivateFieldGet(this, _BetaMessageStream_rejectEndPromise, "f").call(this, error);
this._emit('end');
}
}
_emitFinal() {
const finalMessage = this.receivedMessages.at(-1);
if (finalMessage) {
this._emit('finalMessage', __classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_getFinalMessage).call(this));
}
}
async _fromReadableStream(readableStream, options) {
const signal = options?.signal;
let abortHandler;
if (signal) {
if (signal.aborted)
this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_beginRequest).call(this);
this._connected(null);
const stream = Stream.fromReadableStream(readableStream, this.controller);
for await (const event of stream) {
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_addStreamEvent).call(this, event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
__classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_endRequest).call(this);
}
finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
[(_BetaMessageStream_currentMessageSnapshot = new WeakMap(), _BetaMessageStream_params = new WeakMap(), _BetaMessageStream_connectedPromise = new WeakMap(), _BetaMessageStream_resolveConnectedPromise = new WeakMap(), _BetaMessageStream_rejectConnectedPromise = new WeakMap(), _BetaMessageStream_endPromise = new WeakMap(), _BetaMessageStream_resolveEndPromise = new WeakMap(), _BetaMessageStream_rejectEndPromise = new WeakMap(), _BetaMessageStream_listeners = new WeakMap(), _BetaMessageStream_ended = new WeakMap(), _BetaMessageStream_errored = new WeakMap(), _BetaMessageStream_aborted = new WeakMap(), _BetaMessageStream_catchingPromiseCreated = new WeakMap(), _BetaMessageStream_response = new WeakMap(), _BetaMessageStream_request_id = new WeakMap(), _BetaMessageStream_logger = new WeakMap(), _BetaMessageStream_handleError = new WeakMap(), _BetaMessageStream_instances = new WeakSet(), _BetaMessageStream_getFinalMessage = function _BetaMessageStream_getFinalMessage() {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
return this.receivedMessages.at(-1);
}, _BetaMessageStream_getFinalText = function _BetaMessageStream_getFinalText() {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
const textBlocks = this.receivedMessages
.at(-1)
.content.filter((block) => block.type === 'text')
.map((block) => block.text);
if (textBlocks.length === 0) {
throw new AnthropicError('stream ended without producing a content block with type=text');
}
return textBlocks.join(' ');
}, _BetaMessageStream_beginRequest = function _BetaMessageStream_beginRequest() {
if (this.ended)
return;
__classPrivateFieldSet(this, _BetaMessageStream_currentMessageSnapshot, undefined, "f");
}, _BetaMessageStream_addStreamEvent = function _BetaMessageStream_addStreamEvent(event) {
if (this.ended)
return;
const messageSnapshot = __classPrivateFieldGet(this, _BetaMessageStream_instances, "m", _BetaMessageStream_accumulateMessage).call(this, event);
this._emit('streamEvent', event, messageSnapshot);
switch (event.type) {
case 'content_block_delta': {
const content = messageSnapshot.content.at(-1);
switch (event.delta.type) {
case 'text_delta': {
if (content.type === 'text') {
this._emit('text', event.delta.text, content.text || '');
}
break;
}
case 'citations_delta': {
if (content.type === 'text') {
this._emit('citation', event.delta.citation, content.citations ?? []);
}
break;
}
case 'input_json_delta': {
if (tracksToolInput(content) && content.input) {
this._emit('inputJson', event.delta.partial_json, content.input);
}
break;
}
case 'thinking_delta': {
if (content.type === 'thinking') {
this._emit('thinking', event.delta.thinking, content.thinking);
}
break;
}
case 'signature_delta': {
if (content.type === 'thinking') {
this._emit('signature', content.signature);
}
break;
}
case 'compaction_delta': {
if (content.type === 'compaction' && content.content) {
this._emit('compaction', content.content);
}
break;
}
default:
checkNever(event.delta);
}
break;
}
case 'message_stop': {
this._addMessageParam(messageSnapshot);
this._addMessage(maybeParseBetaMessage(messageSnapshot, __classPrivateFieldGet(this, _BetaMessageStream_params, "f"), { logger: __classPrivateFieldGet(this, _BetaMessageStream_logger, "f") }), true);
break;
}
case 'content_block_stop': {
this._emit('contentBlock', messageSnapshot.content.at(-1));
break;
}
case 'message_start': {
__classPrivateFieldSet(this, _BetaMessageStream_currentMessageSnapshot, messageSnapshot, "f");
break;
}
case 'content_block_start':
case 'message_delta':
break;
}
}, _BetaMessageStream_endRequest = function _BetaMessageStream_endRequest() {
if (this.ended) {
throw new AnthropicError(`stream has ended, this shouldn't happen`);
}
const snapshot = __classPrivateFieldGet(this, _BetaMessageStream_currentMessageSnapshot, "f");
if (!snapshot) {
throw new AnthropicError(`request ended without sending any chunks`);
}
__classPrivateFieldSet(this, _BetaMessageStream_currentMessageSnapshot, undefined, "f");
return maybeParseBetaMessage(snapshot, __classPrivateFieldGet(this, _BetaMessageStream_params, "f"), { logger: __classPrivateFieldGet(this, _BetaMessageStream_logger, "f") });
}, _BetaMessageStream_accumulateMessage = function _BetaMessageStream_accumulateMessage(event) {
let snapshot = __classPrivateFieldGet(this, _BetaMessageStream_currentMessageSnapshot, "f");
if (event.type === 'message_start') {
if (snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before receiving "message_stop"`);
}
return event.message;
}
if (!snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before "message_start"`);
}
switch (event.type) {
case 'message_stop':
return snapshot;
case 'message_delta':
snapshot.container = event.delta.container;
snapshot.stop_reason = event.delta.stop_reason;
snapshot.stop_sequence = event.delta.stop_sequence;
snapshot.usage.output_tokens = event.usage.output_tokens;
snapshot.context_management = event.context_management;
if (event.usage.input_tokens != null) {
snapshot.usage.input_tokens = event.usage.input_tokens;
}
if (event.usage.cache_creation_input_tokens != null) {
snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens;
}
if (event.usage.cache_read_input_tokens != null) {
snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens;
}
if (event.usage.server_tool_use != null) {
snapshot.usage.server_tool_use = event.usage.server_tool_use;
}
if (event.usage.iterations != null) {
snapshot.usage.iterations = event.usage.iterations;
}
return snapshot;
case 'content_block_start':
snapshot.content.push(event.content_block);
return snapshot;
case 'content_block_delta': {
const snapshotContent = snapshot.content.at(event.index);
switch (event.delta.type) {
case 'text_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
text: (snapshotContent.text || '') + event.delta.text,
};
}
break;
}
case 'citations_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
};
}
break;
}
case 'input_json_delta': {
if (snapshotContent && tracksToolInput(snapshotContent)) {
// we need to keep track of the raw JSON string as well so that we can
// re-parse it for each delta, for now we just store it as an untyped
// non-enumerable property on the snapshot
let jsonBuf = snapshotContent[JSON_BUF_PROPERTY] || '';
jsonBuf += event.delta.partial_json;
const newContent = { ...snapshotContent };
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
value: jsonBuf,
enumerable: false,
writable: true,
});
if (jsonBuf) {
try {
newContent.input = partialParse(jsonBuf);
}
catch (err) {
const error = new AnthropicError(`Unable to parse tool parameter JSON from model. Please retry your request or adjust your prompt. Error: ${err}. JSON: ${jsonBuf}`);
__classPrivateFieldGet(this, _BetaMessageStream_handleError, "f").call(this, error);
}
}
snapshot.content[event.index] = newContent;
}
break;
}
case 'thinking_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
thinking: snapshotContent.thinking + event.delta.thinking,
};
}
break;
}
case 'signature_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
signature: event.delta.signature,
};
}
break;
}
case 'compaction_delta': {
if (snapshotContent?.type === 'compaction') {
snapshot.content[event.index] = {
...snapshotContent,
content: (snapshotContent.content || '') + event.delta.content,
};
}
break;
}
default:
checkNever(event.delta);
}
return snapshot;
}
case 'content_block_stop':
return snapshot;
}
}, Symbol.asyncIterator)]() {
const pushQueue = [];
const readQueue = [];
let done = false;
this.on('streamEvent', (event) => {
const reader = readQueue.shift();
if (reader) {
reader.resolve(event);
}
else {
pushQueue.push(event);
}
});
this.on('end', () => {
done = true;
for (const reader of readQueue) {
reader.resolve(undefined);
}
readQueue.length = 0;
});
this.on('abort', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
this.on('error', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
return {
next: async () => {
if (!pushQueue.length) {
if (done) {
return { value: undefined, done: true };
}
return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk) => (chunk ? { value: chunk, done: false } : { value: undefined, done: true }));
}
const chunk = pushQueue.shift();
return { value: chunk, done: false };
},
return: async () => {
this.abort();
return { value: undefined, done: true };
},
};
}
toReadableStream() {
const stream = new Stream(this[Symbol.asyncIterator].bind(this), this.controller);
return stream.toReadableStream();
}
}
// used to ensure exhaustive case matching without throwing a runtime error
function checkNever(x) { }
//# sourceMappingURL=BetaMessageStream.mjs.map

View File

@@ -1,591 +0,0 @@
var _MessageStream_instances, _MessageStream_currentMessageSnapshot, _MessageStream_params, _MessageStream_connectedPromise, _MessageStream_resolveConnectedPromise, _MessageStream_rejectConnectedPromise, _MessageStream_endPromise, _MessageStream_resolveEndPromise, _MessageStream_rejectEndPromise, _MessageStream_listeners, _MessageStream_ended, _MessageStream_errored, _MessageStream_aborted, _MessageStream_catchingPromiseCreated, _MessageStream_response, _MessageStream_request_id, _MessageStream_logger, _MessageStream_getFinalMessage, _MessageStream_getFinalText, _MessageStream_handleError, _MessageStream_beginRequest, _MessageStream_addStreamEvent, _MessageStream_endRequest, _MessageStream_accumulateMessage;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../internal/tslib.mjs";
import { isAbortError } from "../internal/errors.mjs";
import { AnthropicError, APIUserAbortError } from "../error.mjs";
import { Stream } from "../streaming.mjs";
import { partialParse } from "../_vendor/partial-json-parser/parser.mjs";
import { maybeParseMessage } from "./parser.mjs";
const JSON_BUF_PROPERTY = '__json_buf';
function tracksToolInput(content) {
return content.type === 'tool_use' || content.type === 'server_tool_use';
}
export class MessageStream {
constructor(params, opts) {
_MessageStream_instances.add(this);
this.messages = [];
this.receivedMessages = [];
_MessageStream_currentMessageSnapshot.set(this, void 0);
_MessageStream_params.set(this, null);
this.controller = new AbortController();
_MessageStream_connectedPromise.set(this, void 0);
_MessageStream_resolveConnectedPromise.set(this, () => { });
_MessageStream_rejectConnectedPromise.set(this, () => { });
_MessageStream_endPromise.set(this, void 0);
_MessageStream_resolveEndPromise.set(this, () => { });
_MessageStream_rejectEndPromise.set(this, () => { });
_MessageStream_listeners.set(this, {});
_MessageStream_ended.set(this, false);
_MessageStream_errored.set(this, false);
_MessageStream_aborted.set(this, false);
_MessageStream_catchingPromiseCreated.set(this, false);
_MessageStream_response.set(this, void 0);
_MessageStream_request_id.set(this, void 0);
_MessageStream_logger.set(this, void 0);
_MessageStream_handleError.set(this, (error) => {
__classPrivateFieldSet(this, _MessageStream_errored, true, "f");
if (isAbortError(error)) {
error = new APIUserAbortError();
}
if (error instanceof APIUserAbortError) {
__classPrivateFieldSet(this, _MessageStream_aborted, true, "f");
return this._emit('abort', error);
}
if (error instanceof AnthropicError) {
return this._emit('error', error);
}
if (error instanceof Error) {
const anthropicError = new AnthropicError(error.message);
// @ts-ignore
anthropicError.cause = error;
return this._emit('error', anthropicError);
}
return this._emit('error', new AnthropicError(String(error)));
});
__classPrivateFieldSet(this, _MessageStream_connectedPromise, new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _MessageStream_resolveConnectedPromise, resolve, "f");
__classPrivateFieldSet(this, _MessageStream_rejectConnectedPromise, reject, "f");
}), "f");
__classPrivateFieldSet(this, _MessageStream_endPromise, new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _MessageStream_resolveEndPromise, resolve, "f");
__classPrivateFieldSet(this, _MessageStream_rejectEndPromise, reject, "f");
}), "f");
// Don't let these promises cause unhandled rejection errors.
// we will manually cause an unhandled rejection error later
// if the user hasn't registered any error listener or called
// any promise-returning method.
__classPrivateFieldGet(this, _MessageStream_connectedPromise, "f").catch(() => { });
__classPrivateFieldGet(this, _MessageStream_endPromise, "f").catch(() => { });
__classPrivateFieldSet(this, _MessageStream_params, params, "f");
__classPrivateFieldSet(this, _MessageStream_logger, opts?.logger ?? console, "f");
}
get response() {
return __classPrivateFieldGet(this, _MessageStream_response, "f");
}
get request_id() {
return __classPrivateFieldGet(this, _MessageStream_request_id, "f");
}
/**
* Returns the `MessageStream` data, the raw `Response` instance and the ID of the request,
* returned vie the `request-id` header which is useful for debugging requests and resporting
* issues to Anthropic.
*
* This is the same as the `APIPromise.withResponse()` method.
*
* This method will raise an error if you created the stream using `MessageStream.fromReadableStream`
* as no `Response` is available.
*/
async withResponse() {
__classPrivateFieldSet(this, _MessageStream_catchingPromiseCreated, true, "f");
const response = await __classPrivateFieldGet(this, _MessageStream_connectedPromise, "f");
if (!response) {
throw new Error('Could not resolve a `Response` object');
}
return {
data: this,
response,
request_id: response.headers.get('request-id'),
};
}
/**
* Intended for use on the frontend, consuming a stream produced with
* `.toReadableStream()` on the backend.
*
* Note that messages sent to the model do not appear in `.on('message')`
* in this context.
*/
static fromReadableStream(stream) {
const runner = new MessageStream(null);
runner._run(() => runner._fromReadableStream(stream));
return runner;
}
static createMessage(messages, params, options, { logger } = {}) {
const runner = new MessageStream(params, { logger });
for (const message of params.messages) {
runner._addMessageParam(message);
}
__classPrivateFieldSet(runner, _MessageStream_params, { ...params, stream: true }, "f");
runner._run(() => runner._createMessage(messages, { ...params, stream: true }, { ...options, headers: { ...options?.headers, 'X-Stainless-Helper-Method': 'stream' } }));
return runner;
}
_run(executor) {
executor().then(() => {
this._emitFinal();
this._emit('end');
}, __classPrivateFieldGet(this, _MessageStream_handleError, "f"));
}
_addMessageParam(message) {
this.messages.push(message);
}
_addMessage(message, emit = true) {
this.receivedMessages.push(message);
if (emit) {
this._emit('message', message);
}
}
async _createMessage(messages, params, options) {
const signal = options?.signal;
let abortHandler;
if (signal) {
if (signal.aborted)
this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_beginRequest).call(this);
const { response, data: stream } = await messages
.create({ ...params, stream: true }, { ...options, signal: this.controller.signal })
.withResponse();
this._connected(response);
for await (const event of stream) {
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_addStreamEvent).call(this, event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_endRequest).call(this);
}
finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
_connected(response) {
if (this.ended)
return;
__classPrivateFieldSet(this, _MessageStream_response, response, "f");
__classPrivateFieldSet(this, _MessageStream_request_id, response?.headers.get('request-id'), "f");
__classPrivateFieldGet(this, _MessageStream_resolveConnectedPromise, "f").call(this, response);
this._emit('connect');
}
get ended() {
return __classPrivateFieldGet(this, _MessageStream_ended, "f");
}
get errored() {
return __classPrivateFieldGet(this, _MessageStream_errored, "f");
}
get aborted() {
return __classPrivateFieldGet(this, _MessageStream_aborted, "f");
}
abort() {
this.controller.abort();
}
/**
* Adds the listener function to the end of the listeners array for the event.
* No checks are made to see if the listener has already been added. Multiple calls passing
* the same combination of event and listener will result in the listener being added, and
* called, multiple times.
* @returns this MessageStream, so that calls can be chained
*/
on(event, listener) {
const listeners = __classPrivateFieldGet(this, _MessageStream_listeners, "f")[event] || (__classPrivateFieldGet(this, _MessageStream_listeners, "f")[event] = []);
listeners.push({ listener });
return this;
}
/**
* Removes the specified listener from the listener array for the event.
* off() will remove, at most, one instance of a listener from the listener array. If any single
* listener has been added multiple times to the listener array for the specified event, then
* off() must be called multiple times to remove each instance.
* @returns this MessageStream, so that calls can be chained
*/
off(event, listener) {
const listeners = __classPrivateFieldGet(this, _MessageStream_listeners, "f")[event];
if (!listeners)
return this;
const index = listeners.findIndex((l) => l.listener === listener);
if (index >= 0)
listeners.splice(index, 1);
return this;
}
/**
* Adds a one-time listener function for the event. The next time the event is triggered,
* this listener is removed and then invoked.
* @returns this MessageStream, so that calls can be chained
*/
once(event, listener) {
const listeners = __classPrivateFieldGet(this, _MessageStream_listeners, "f")[event] || (__classPrivateFieldGet(this, _MessageStream_listeners, "f")[event] = []);
listeners.push({ listener, once: true });
return this;
}
/**
* This is similar to `.once()`, but returns a Promise that resolves the next time
* the event is triggered, instead of calling a listener callback.
* @returns a Promise that resolves the next time given event is triggered,
* or rejects if an error is emitted. (If you request the 'error' event,
* returns a promise that resolves with the error).
*
* Example:
*
* const message = await stream.emitted('message') // rejects if the stream errors
*/
emitted(event) {
return new Promise((resolve, reject) => {
__classPrivateFieldSet(this, _MessageStream_catchingPromiseCreated, true, "f");
if (event !== 'error')
this.once('error', reject);
this.once(event, resolve);
});
}
async done() {
__classPrivateFieldSet(this, _MessageStream_catchingPromiseCreated, true, "f");
await __classPrivateFieldGet(this, _MessageStream_endPromise, "f");
}
get currentMessage() {
return __classPrivateFieldGet(this, _MessageStream_currentMessageSnapshot, "f");
}
/**
* @returns a promise that resolves with the the final assistant Message response,
* or rejects if an error occurred or the stream ended prematurely without producing a Message.
* If structured outputs were used, this will be a ParsedMessage with a `parsed_output` field.
*/
async finalMessage() {
await this.done();
return __classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_getFinalMessage).call(this);
}
/**
* @returns a promise that resolves with the the final assistant Message's text response, concatenated
* together if there are more than one text blocks.
* Rejects if an error occurred or the stream ended prematurely without producing a Message.
*/
async finalText() {
await this.done();
return __classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_getFinalText).call(this);
}
_emit(event, ...args) {
// make sure we don't emit any MessageStreamEvents after end
if (__classPrivateFieldGet(this, _MessageStream_ended, "f"))
return;
if (event === 'end') {
__classPrivateFieldSet(this, _MessageStream_ended, true, "f");
__classPrivateFieldGet(this, _MessageStream_resolveEndPromise, "f").call(this);
}
const listeners = __classPrivateFieldGet(this, _MessageStream_listeners, "f")[event];
if (listeners) {
__classPrivateFieldGet(this, _MessageStream_listeners, "f")[event] = listeners.filter((l) => !l.once);
listeners.forEach(({ listener }) => listener(...args));
}
if (event === 'abort') {
const error = args[0];
if (!__classPrivateFieldGet(this, _MessageStream_catchingPromiseCreated, "f") && !listeners?.length) {
Promise.reject(error);
}
__classPrivateFieldGet(this, _MessageStream_rejectConnectedPromise, "f").call(this, error);
__classPrivateFieldGet(this, _MessageStream_rejectEndPromise, "f").call(this, error);
this._emit('end');
return;
}
if (event === 'error') {
// NOTE: _emit('error', error) should only be called from #handleError().
const error = args[0];
if (!__classPrivateFieldGet(this, _MessageStream_catchingPromiseCreated, "f") && !listeners?.length) {
// Trigger an unhandled rejection if the user hasn't registered any error handlers.
// If you are seeing stack traces here, make sure to handle errors via either:
// - runner.on('error', () => ...)
// - await runner.done()
// - await runner.final...()
// - etc.
Promise.reject(error);
}
__classPrivateFieldGet(this, _MessageStream_rejectConnectedPromise, "f").call(this, error);
__classPrivateFieldGet(this, _MessageStream_rejectEndPromise, "f").call(this, error);
this._emit('end');
}
}
_emitFinal() {
const finalMessage = this.receivedMessages.at(-1);
if (finalMessage) {
this._emit('finalMessage', __classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_getFinalMessage).call(this));
}
}
async _fromReadableStream(readableStream, options) {
const signal = options?.signal;
let abortHandler;
if (signal) {
if (signal.aborted)
this.controller.abort();
abortHandler = this.controller.abort.bind(this.controller);
signal.addEventListener('abort', abortHandler);
}
try {
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_beginRequest).call(this);
this._connected(null);
const stream = Stream.fromReadableStream(readableStream, this.controller);
for await (const event of stream) {
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_addStreamEvent).call(this, event);
}
if (stream.controller.signal?.aborted) {
throw new APIUserAbortError();
}
__classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_endRequest).call(this);
}
finally {
if (signal && abortHandler) {
signal.removeEventListener('abort', abortHandler);
}
}
}
[(_MessageStream_currentMessageSnapshot = new WeakMap(), _MessageStream_params = new WeakMap(), _MessageStream_connectedPromise = new WeakMap(), _MessageStream_resolveConnectedPromise = new WeakMap(), _MessageStream_rejectConnectedPromise = new WeakMap(), _MessageStream_endPromise = new WeakMap(), _MessageStream_resolveEndPromise = new WeakMap(), _MessageStream_rejectEndPromise = new WeakMap(), _MessageStream_listeners = new WeakMap(), _MessageStream_ended = new WeakMap(), _MessageStream_errored = new WeakMap(), _MessageStream_aborted = new WeakMap(), _MessageStream_catchingPromiseCreated = new WeakMap(), _MessageStream_response = new WeakMap(), _MessageStream_request_id = new WeakMap(), _MessageStream_logger = new WeakMap(), _MessageStream_handleError = new WeakMap(), _MessageStream_instances = new WeakSet(), _MessageStream_getFinalMessage = function _MessageStream_getFinalMessage() {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
return this.receivedMessages.at(-1);
}, _MessageStream_getFinalText = function _MessageStream_getFinalText() {
if (this.receivedMessages.length === 0) {
throw new AnthropicError('stream ended without producing a Message with role=assistant');
}
const textBlocks = this.receivedMessages
.at(-1)
.content.filter((block) => block.type === 'text')
.map((block) => block.text);
if (textBlocks.length === 0) {
throw new AnthropicError('stream ended without producing a content block with type=text');
}
return textBlocks.join(' ');
}, _MessageStream_beginRequest = function _MessageStream_beginRequest() {
if (this.ended)
return;
__classPrivateFieldSet(this, _MessageStream_currentMessageSnapshot, undefined, "f");
}, _MessageStream_addStreamEvent = function _MessageStream_addStreamEvent(event) {
if (this.ended)
return;
const messageSnapshot = __classPrivateFieldGet(this, _MessageStream_instances, "m", _MessageStream_accumulateMessage).call(this, event);
this._emit('streamEvent', event, messageSnapshot);
switch (event.type) {
case 'content_block_delta': {
const content = messageSnapshot.content.at(-1);
switch (event.delta.type) {
case 'text_delta': {
if (content.type === 'text') {
this._emit('text', event.delta.text, content.text || '');
}
break;
}
case 'citations_delta': {
if (content.type === 'text') {
this._emit('citation', event.delta.citation, content.citations ?? []);
}
break;
}
case 'input_json_delta': {
if (tracksToolInput(content) && content.input) {
this._emit('inputJson', event.delta.partial_json, content.input);
}
break;
}
case 'thinking_delta': {
if (content.type === 'thinking') {
this._emit('thinking', event.delta.thinking, content.thinking);
}
break;
}
case 'signature_delta': {
if (content.type === 'thinking') {
this._emit('signature', content.signature);
}
break;
}
default:
checkNever(event.delta);
}
break;
}
case 'message_stop': {
this._addMessageParam(messageSnapshot);
this._addMessage(maybeParseMessage(messageSnapshot, __classPrivateFieldGet(this, _MessageStream_params, "f"), { logger: __classPrivateFieldGet(this, _MessageStream_logger, "f") }), true);
break;
}
case 'content_block_stop': {
this._emit('contentBlock', messageSnapshot.content.at(-1));
break;
}
case 'message_start': {
__classPrivateFieldSet(this, _MessageStream_currentMessageSnapshot, messageSnapshot, "f");
break;
}
case 'content_block_start':
case 'message_delta':
break;
}
}, _MessageStream_endRequest = function _MessageStream_endRequest() {
if (this.ended) {
throw new AnthropicError(`stream has ended, this shouldn't happen`);
}
const snapshot = __classPrivateFieldGet(this, _MessageStream_currentMessageSnapshot, "f");
if (!snapshot) {
throw new AnthropicError(`request ended without sending any chunks`);
}
__classPrivateFieldSet(this, _MessageStream_currentMessageSnapshot, undefined, "f");
return maybeParseMessage(snapshot, __classPrivateFieldGet(this, _MessageStream_params, "f"), { logger: __classPrivateFieldGet(this, _MessageStream_logger, "f") });
}, _MessageStream_accumulateMessage = function _MessageStream_accumulateMessage(event) {
let snapshot = __classPrivateFieldGet(this, _MessageStream_currentMessageSnapshot, "f");
if (event.type === 'message_start') {
if (snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before receiving "message_stop"`);
}
return event.message;
}
if (!snapshot) {
throw new AnthropicError(`Unexpected event order, got ${event.type} before "message_start"`);
}
switch (event.type) {
case 'message_stop':
return snapshot;
case 'message_delta':
snapshot.stop_reason = event.delta.stop_reason;
snapshot.stop_sequence = event.delta.stop_sequence;
snapshot.usage.output_tokens = event.usage.output_tokens;
// Update other usage fields if they exist in the event
if (event.usage.input_tokens != null) {
snapshot.usage.input_tokens = event.usage.input_tokens;
}
if (event.usage.cache_creation_input_tokens != null) {
snapshot.usage.cache_creation_input_tokens = event.usage.cache_creation_input_tokens;
}
if (event.usage.cache_read_input_tokens != null) {
snapshot.usage.cache_read_input_tokens = event.usage.cache_read_input_tokens;
}
if (event.usage.server_tool_use != null) {
snapshot.usage.server_tool_use = event.usage.server_tool_use;
}
return snapshot;
case 'content_block_start':
snapshot.content.push({ ...event.content_block });
return snapshot;
case 'content_block_delta': {
const snapshotContent = snapshot.content.at(event.index);
switch (event.delta.type) {
case 'text_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
text: (snapshotContent.text || '') + event.delta.text,
};
}
break;
}
case 'citations_delta': {
if (snapshotContent?.type === 'text') {
snapshot.content[event.index] = {
...snapshotContent,
citations: [...(snapshotContent.citations ?? []), event.delta.citation],
};
}
break;
}
case 'input_json_delta': {
if (snapshotContent && tracksToolInput(snapshotContent)) {
// we need to keep track of the raw JSON string as well so that we can
// re-parse it for each delta, for now we just store it as an untyped
// non-enumerable property on the snapshot
let jsonBuf = snapshotContent[JSON_BUF_PROPERTY] || '';
jsonBuf += event.delta.partial_json;
const newContent = { ...snapshotContent };
Object.defineProperty(newContent, JSON_BUF_PROPERTY, {
value: jsonBuf,
enumerable: false,
writable: true,
});
if (jsonBuf) {
newContent.input = partialParse(jsonBuf);
}
snapshot.content[event.index] = newContent;
}
break;
}
case 'thinking_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
thinking: snapshotContent.thinking + event.delta.thinking,
};
}
break;
}
case 'signature_delta': {
if (snapshotContent?.type === 'thinking') {
snapshot.content[event.index] = {
...snapshotContent,
signature: event.delta.signature,
};
}
break;
}
default:
checkNever(event.delta);
}
return snapshot;
}
case 'content_block_stop':
return snapshot;
}
}, Symbol.asyncIterator)]() {
const pushQueue = [];
const readQueue = [];
let done = false;
this.on('streamEvent', (event) => {
const reader = readQueue.shift();
if (reader) {
reader.resolve(event);
}
else {
pushQueue.push(event);
}
});
this.on('end', () => {
done = true;
for (const reader of readQueue) {
reader.resolve(undefined);
}
readQueue.length = 0;
});
this.on('abort', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
this.on('error', (err) => {
done = true;
for (const reader of readQueue) {
reader.reject(err);
}
readQueue.length = 0;
});
return {
next: async () => {
if (!pushQueue.length) {
if (done) {
return { value: undefined, done: true };
}
return new Promise((resolve, reject) => readQueue.push({ resolve, reject })).then((chunk) => (chunk ? { value: chunk, done: false } : { value: undefined, done: true }));
}
const chunk = pushQueue.shift();
return { value: chunk, done: false };
},
return: async () => {
this.abort();
return { value: undefined, done: true };
},
};
}
toReadableStream() {
const stream = new Stream(this[Symbol.asyncIterator].bind(this), this.controller);
return stream.toReadableStream();
}
}
// used to ensure exhaustive case matching without throwing a runtime error
function checkNever(x) { }
//# sourceMappingURL=MessageStream.mjs.map

View File

@@ -1,75 +0,0 @@
import { AnthropicError } from "../core/error.mjs";
function getOutputFormat(params) {
// Prefer output_format (deprecated) over output_config.format for backward compatibility
return params?.output_format ?? params?.output_config?.format;
}
export function maybeParseBetaMessage(message, params, opts) {
const outputFormat = getOutputFormat(params);
if (!params || !('parse' in (outputFormat ?? {}))) {
return {
...message,
content: message.content.map((block) => {
if (block.type === 'text') {
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: null,
enumerable: false,
});
return Object.defineProperty(parsedBlock, 'parsed', {
get() {
opts.logger.warn('The `parsed` property on `text` blocks is deprecated, please use `parsed_output` instead.');
return null;
},
enumerable: false,
});
}
return block;
}),
parsed_output: null,
};
}
return parseBetaMessage(message, params, opts);
}
export function parseBetaMessage(message, params, opts) {
let firstParsedOutput = null;
const content = message.content.map((block) => {
if (block.type === 'text') {
const parsedOutput = parseBetaOutputFormat(params, block.text);
if (firstParsedOutput === null) {
firstParsedOutput = parsedOutput;
}
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: parsedOutput,
enumerable: false,
});
return Object.defineProperty(parsedBlock, 'parsed', {
get() {
opts.logger.warn('The `parsed` property on `text` blocks is deprecated, please use `parsed_output` instead.');
return parsedOutput;
},
enumerable: false,
});
}
return block;
});
return {
...message,
content,
parsed_output: firstParsedOutput,
};
}
function parseBetaOutputFormat(params, content) {
const outputFormat = getOutputFormat(params);
if (outputFormat?.type !== 'json_schema') {
return null;
}
try {
if ('parse' in outputFormat) {
return outputFormat.parse(content);
}
return JSON.parse(content);
}
catch (error) {
throw new AnthropicError(`Failed to parse structured output: ${error}`);
}
}
//# sourceMappingURL=beta-parser.mjs.map

View File

@@ -1,62 +0,0 @@
import { AnthropicError } from "../core/error.mjs";
function getOutputFormat(params) {
return params?.output_config?.format;
}
export function maybeParseMessage(message, params, opts) {
const outputFormat = getOutputFormat(params);
if (!params || !('parse' in (outputFormat ?? {}))) {
return {
...message,
content: message.content.map((block) => {
if (block.type === 'text') {
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: null,
enumerable: false,
});
return parsedBlock;
}
return block;
}),
parsed_output: null,
};
}
return parseMessage(message, params, opts);
}
export function parseMessage(message, params, opts) {
let firstParsedOutput = null;
const content = message.content.map((block) => {
if (block.type === 'text') {
const parsedOutput = parseOutputFormat(params, block.text);
if (firstParsedOutput === null) {
firstParsedOutput = parsedOutput;
}
const parsedBlock = Object.defineProperty({ ...block }, 'parsed_output', {
value: parsedOutput,
enumerable: false,
});
return parsedBlock;
}
return block;
});
return {
...message,
content,
parsed_output: firstParsedOutput,
};
}
function parseOutputFormat(params, content) {
const outputFormat = getOutputFormat(params);
if (outputFormat?.type !== 'json_schema') {
return null;
}
try {
if ('parse' in outputFormat) {
return outputFormat.parse(content);
}
return JSON.parse(content);
}
catch (error) {
throw new AnthropicError(`Failed to parse structured output: ${error}`);
}
}
//# sourceMappingURL=parser.mjs.map

View File

@@ -1,63 +0,0 @@
/**
* Shared utilities for tracking SDK helper usage.
*/
/**
* Symbol used to mark objects created by SDK helpers for tracking.
* The value is the helper name (e.g., 'mcpTool', 'betaZodTool').
*/
export const SDK_HELPER_SYMBOL = Symbol('anthropic.sdk.stainlessHelper');
export function wasCreatedByStainlessHelper(value) {
return typeof value === 'object' && value !== null && SDK_HELPER_SYMBOL in value;
}
/**
* Collects helper names from tools and messages arrays.
* Returns a deduplicated array of helper names found.
*/
export function collectStainlessHelpers(tools, messages) {
const helpers = new Set();
// Collect from tools
if (tools) {
for (const tool of tools) {
if (wasCreatedByStainlessHelper(tool)) {
helpers.add(tool[SDK_HELPER_SYMBOL]);
}
}
}
// Collect from messages and their content blocks
if (messages) {
for (const message of messages) {
if (wasCreatedByStainlessHelper(message)) {
helpers.add(message[SDK_HELPER_SYMBOL]);
}
if (Array.isArray(message.content)) {
for (const block of message.content) {
if (wasCreatedByStainlessHelper(block)) {
helpers.add(block[SDK_HELPER_SYMBOL]);
}
}
}
}
}
return Array.from(helpers);
}
/**
* Builds x-stainless-helper header value from tools and messages.
* Returns an empty object if no helpers are found.
*/
export function stainlessHelperHeader(tools, messages) {
const helpers = collectStainlessHelpers(tools, messages);
if (helpers.length === 0)
return {};
return { 'x-stainless-helper': helpers.join(', ') };
}
/**
* Builds x-stainless-helper header value from a file object.
* Returns an empty object if the file is not marked with a helper.
*/
export function stainlessHelperHeaderFromFile(file) {
if (wasCreatedByStainlessHelper(file)) {
return { 'x-stainless-helper': file[SDK_HELPER_SYMBOL] };
}
return {};
}
//# sourceMappingURL=stainless-helper-header.mjs.map

View File

@@ -1,372 +0,0 @@
var _BetaToolRunner_instances, _BetaToolRunner_consumed, _BetaToolRunner_mutated, _BetaToolRunner_state, _BetaToolRunner_options, _BetaToolRunner_message, _BetaToolRunner_toolResponse, _BetaToolRunner_completion, _BetaToolRunner_iterationCount, _BetaToolRunner_checkAndCompact, _BetaToolRunner_generateToolResponse;
import { __classPrivateFieldGet, __classPrivateFieldSet } from "../../internal/tslib.mjs";
import { ToolError } from "./ToolError.mjs";
import { AnthropicError } from "../../core/error.mjs";
import { buildHeaders } from "../../internal/headers.mjs";
import { DEFAULT_SUMMARY_PROMPT, DEFAULT_TOKEN_THRESHOLD } from "./CompactionControl.mjs";
import { collectStainlessHelpers } from "../stainless-helper-header.mjs";
/**
* Just Promise.withResolvers(), which is not available in all environments.
*/
function promiseWithResolvers() {
let resolve;
let reject;
const promise = new Promise((res, rej) => {
resolve = res;
reject = rej;
});
return { promise, resolve: resolve, reject: reject };
}
/**
* A ToolRunner handles the automatic conversation loop between the assistant and tools.
*
* A ToolRunner is an async iterable that yields either BetaMessage or BetaMessageStream objects
* depending on the streaming configuration.
*/
export class BetaToolRunner {
constructor(client, params, options) {
_BetaToolRunner_instances.add(this);
this.client = client;
/** Whether the async iterator has been consumed */
_BetaToolRunner_consumed.set(this, false);
/** Whether parameters have been mutated since the last API call */
_BetaToolRunner_mutated.set(this, false);
/** Current state containing the request parameters */
_BetaToolRunner_state.set(this, void 0);
_BetaToolRunner_options.set(this, void 0);
/** Promise for the last message received from the assistant */
_BetaToolRunner_message.set(this, void 0);
/** Cached tool response to avoid redundant executions */
_BetaToolRunner_toolResponse.set(this, void 0);
/** Promise resolvers for waiting on completion */
_BetaToolRunner_completion.set(this, void 0);
/** Number of iterations (API requests) made so far */
_BetaToolRunner_iterationCount.set(this, 0);
__classPrivateFieldSet(this, _BetaToolRunner_state, {
params: {
// You can't clone the entire params since there are functions as handlers.
// You also don't really need to clone params.messages, but it probably will prevent a foot gun
// somewhere.
...params,
messages: structuredClone(params.messages),
},
}, "f");
const helpers = collectStainlessHelpers(params.tools, params.messages);
const helperValue = ['BetaToolRunner', ...helpers].join(', ');
__classPrivateFieldSet(this, _BetaToolRunner_options, {
...options,
headers: buildHeaders([{ 'x-stainless-helper': helperValue }, options?.headers]),
}, "f");
__classPrivateFieldSet(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
}
async *[(_BetaToolRunner_consumed = new WeakMap(), _BetaToolRunner_mutated = new WeakMap(), _BetaToolRunner_state = new WeakMap(), _BetaToolRunner_options = new WeakMap(), _BetaToolRunner_message = new WeakMap(), _BetaToolRunner_toolResponse = new WeakMap(), _BetaToolRunner_completion = new WeakMap(), _BetaToolRunner_iterationCount = new WeakMap(), _BetaToolRunner_instances = new WeakSet(), _BetaToolRunner_checkAndCompact = async function _BetaToolRunner_checkAndCompact() {
const compactionControl = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.compactionControl;
if (!compactionControl || !compactionControl.enabled) {
return false;
}
let tokensUsed = 0;
if (__classPrivateFieldGet(this, _BetaToolRunner_message, "f") !== undefined) {
try {
const message = await __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
const totalInputTokens = message.usage.input_tokens +
(message.usage.cache_creation_input_tokens ?? 0) +
(message.usage.cache_read_input_tokens ?? 0);
tokensUsed = totalInputTokens + message.usage.output_tokens;
}
catch {
// If we can't get the message, skip compaction
return false;
}
}
const threshold = compactionControl.contextTokenThreshold ?? DEFAULT_TOKEN_THRESHOLD;
if (tokensUsed < threshold) {
return false;
}
const model = compactionControl.model ?? __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.model;
const summaryPrompt = compactionControl.summaryPrompt ?? DEFAULT_SUMMARY_PROMPT;
const messages = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages;
if (messages[messages.length - 1].role === 'assistant') {
// Remove tool_use blocks from the last message to avoid 400 error
// (tool_use requires tool_result, which we don't have yet)
const lastMessage = messages[messages.length - 1];
if (Array.isArray(lastMessage.content)) {
const nonToolBlocks = lastMessage.content.filter((block) => block.type !== 'tool_use');
if (nonToolBlocks.length === 0) {
// If all blocks were tool_use, just remove the message entirely
messages.pop();
}
else {
lastMessage.content = nonToolBlocks;
}
}
}
const response = await this.client.beta.messages.create({
model,
messages: [
...messages,
{
role: 'user',
content: [
{
type: 'text',
text: summaryPrompt,
},
],
},
],
max_tokens: __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_tokens,
}, {
headers: { 'x-stainless-helper': 'compaction' },
});
if (response.content[0]?.type !== 'text') {
throw new AnthropicError('Expected text response for compaction');
}
__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages = [
{
role: 'user',
content: response.content,
},
];
return true;
}, Symbol.asyncIterator)]() {
var _a;
if (__classPrivateFieldGet(this, _BetaToolRunner_consumed, "f")) {
throw new AnthropicError('Cannot iterate over a consumed stream');
}
__classPrivateFieldSet(this, _BetaToolRunner_consumed, true, "f");
__classPrivateFieldSet(this, _BetaToolRunner_mutated, true, "f");
__classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
try {
while (true) {
let stream;
try {
if (__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_iterations &&
__classPrivateFieldGet(this, _BetaToolRunner_iterationCount, "f") >= __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.max_iterations) {
break;
}
__classPrivateFieldSet(this, _BetaToolRunner_mutated, false, "f");
__classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
__classPrivateFieldSet(this, _BetaToolRunner_iterationCount, (_a = __classPrivateFieldGet(this, _BetaToolRunner_iterationCount, "f"), _a++, _a), "f");
__classPrivateFieldSet(this, _BetaToolRunner_message, undefined, "f");
const { max_iterations, compactionControl, ...params } = __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params;
if (params.stream) {
stream = this.client.beta.messages.stream({ ...params }, __classPrivateFieldGet(this, _BetaToolRunner_options, "f"));
__classPrivateFieldSet(this, _BetaToolRunner_message, stream.finalMessage(), "f");
// Make sure that this promise doesn't throw before we get the option to do something about it.
// Error will be caught when we call await this.#message ultimately
__classPrivateFieldGet(this, _BetaToolRunner_message, "f").catch(() => { });
yield stream;
}
else {
__classPrivateFieldSet(this, _BetaToolRunner_message, this.client.beta.messages.create({ ...params, stream: false }, __classPrivateFieldGet(this, _BetaToolRunner_options, "f")), "f");
yield __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
}
const isCompacted = await __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_checkAndCompact).call(this);
if (!isCompacted) {
if (!__classPrivateFieldGet(this, _BetaToolRunner_mutated, "f")) {
const { role, content } = await __classPrivateFieldGet(this, _BetaToolRunner_message, "f");
__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.push({ role, content });
}
const toolMessage = await __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_generateToolResponse).call(this, __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.at(-1));
if (toolMessage) {
__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params.messages.push(toolMessage);
}
else if (!__classPrivateFieldGet(this, _BetaToolRunner_mutated, "f")) {
break;
}
}
}
finally {
if (stream) {
stream.abort();
}
}
}
if (!__classPrivateFieldGet(this, _BetaToolRunner_message, "f")) {
throw new AnthropicError('ToolRunner concluded without a message from the server');
}
__classPrivateFieldGet(this, _BetaToolRunner_completion, "f").resolve(await __classPrivateFieldGet(this, _BetaToolRunner_message, "f"));
}
catch (error) {
__classPrivateFieldSet(this, _BetaToolRunner_consumed, false, "f");
// Silence unhandled promise errors
__classPrivateFieldGet(this, _BetaToolRunner_completion, "f").promise.catch(() => { });
__classPrivateFieldGet(this, _BetaToolRunner_completion, "f").reject(error);
__classPrivateFieldSet(this, _BetaToolRunner_completion, promiseWithResolvers(), "f");
throw error;
}
}
setMessagesParams(paramsOrMutator) {
if (typeof paramsOrMutator === 'function') {
__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params = paramsOrMutator(__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params);
}
else {
__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params = paramsOrMutator;
}
__classPrivateFieldSet(this, _BetaToolRunner_mutated, true, "f");
// Invalidate cached tool response since parameters changed
__classPrivateFieldSet(this, _BetaToolRunner_toolResponse, undefined, "f");
}
/**
* Get the tool response for the last message from the assistant.
* Avoids redundant tool executions by caching results.
*
* @returns A promise that resolves to a BetaMessageParam containing tool results, or null if no tools need to be executed
*
* @example
* const toolResponse = await runner.generateToolResponse();
* if (toolResponse) {
* console.log('Tool results:', toolResponse.content);
* }
*/
async generateToolResponse() {
const message = (await __classPrivateFieldGet(this, _BetaToolRunner_message, "f")) ?? this.params.messages.at(-1);
if (!message) {
return null;
}
return __classPrivateFieldGet(this, _BetaToolRunner_instances, "m", _BetaToolRunner_generateToolResponse).call(this, message);
}
/**
* Wait for the async iterator to complete. This works even if the async iterator hasn't yet started, and
* will wait for an instance to start and go to completion.
*
* @returns A promise that resolves to the final BetaMessage when the iterator completes
*
* @example
* // Start consuming the iterator
* for await (const message of runner) {
* console.log('Message:', message.content);
* }
*
* // Meanwhile, wait for completion from another part of the code
* const finalMessage = await runner.done();
* console.log('Final response:', finalMessage.content);
*/
done() {
return __classPrivateFieldGet(this, _BetaToolRunner_completion, "f").promise;
}
/**
* Returns a promise indicating that the stream is done. Unlike .done(), this will eagerly read the stream:
* * If the iterator has not been consumed, consume the entire iterator and return the final message from the
* assistant.
* * If the iterator has been consumed, waits for it to complete and returns the final message.
*
* @returns A promise that resolves to the final BetaMessage from the conversation
* @throws {AnthropicError} If no messages were processed during the conversation
*
* @example
* const finalMessage = await runner.runUntilDone();
* console.log('Final response:', finalMessage.content);
*/
async runUntilDone() {
// If not yet consumed, start consuming and wait for completion
if (!__classPrivateFieldGet(this, _BetaToolRunner_consumed, "f")) {
for await (const _ of this) {
// Iterator naturally populates this.#message
}
}
// If consumed but not completed, wait for completion
return this.done();
}
/**
* Get the current parameters being used by the ToolRunner.
*
* @returns A readonly view of the current ToolRunnerParams
*
* @example
* const currentParams = runner.params;
* console.log('Current model:', currentParams.model);
* console.log('Message count:', currentParams.messages.length);
*/
get params() {
return __classPrivateFieldGet(this, _BetaToolRunner_state, "f").params;
}
/**
* Add one or more messages to the conversation history.
*
* @param messages - One or more BetaMessageParam objects to add to the conversation
*
* @example
* runner.pushMessages(
* { role: 'user', content: 'Also, what about the weather in NYC?' }
* );
*
* @example
* // Adding multiple messages
* runner.pushMessages(
* { role: 'user', content: 'What about NYC?' },
* { role: 'user', content: 'And Boston?' }
* );
*/
pushMessages(...messages) {
this.setMessagesParams((params) => ({
...params,
messages: [...params.messages, ...messages],
}));
}
/**
* Makes the ToolRunner directly awaitable, equivalent to calling .runUntilDone()
* This allows using `await runner` instead of `await runner.runUntilDone()`
*/
then(onfulfilled, onrejected) {
return this.runUntilDone().then(onfulfilled, onrejected);
}
}
_BetaToolRunner_generateToolResponse = async function _BetaToolRunner_generateToolResponse(lastMessage) {
if (__classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f") !== undefined) {
return __classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f");
}
__classPrivateFieldSet(this, _BetaToolRunner_toolResponse, generateToolResponse(__classPrivateFieldGet(this, _BetaToolRunner_state, "f").params, lastMessage), "f");
return __classPrivateFieldGet(this, _BetaToolRunner_toolResponse, "f");
};
async function generateToolResponse(params, lastMessage = params.messages.at(-1)) {
// Only process if the last message is from the assistant and has tool use blocks
if (!lastMessage ||
lastMessage.role !== 'assistant' ||
!lastMessage.content ||
typeof lastMessage.content === 'string') {
return null;
}
const toolUseBlocks = lastMessage.content.filter((content) => content.type === 'tool_use');
if (toolUseBlocks.length === 0) {
return null;
}
const toolResults = await Promise.all(toolUseBlocks.map(async (toolUse) => {
const tool = params.tools.find((t) => ('name' in t ? t.name : t.mcp_server_name) === toolUse.name);
if (!tool || !('run' in tool)) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: `Error: Tool '${toolUse.name}' not found`,
is_error: true,
};
}
try {
let input = toolUse.input;
if ('parse' in tool && tool.parse) {
input = tool.parse(input);
}
const result = await tool.run(input);
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: result,
};
}
catch (error) {
return {
type: 'tool_result',
tool_use_id: toolUse.id,
content: error instanceof ToolError ?
error.content
: `Error: ${error instanceof Error ? error.message : String(error)}`,
is_error: true,
};
}
}));
return {
role: 'user',
content: toolResults,
};
}
//# sourceMappingURL=BetaToolRunner.mjs.map

View File

@@ -1,25 +0,0 @@
export const DEFAULT_TOKEN_THRESHOLD = 100000;
export const DEFAULT_SUMMARY_PROMPT = `You have been working on the task described above but have not yet completed it. Write a continuation summary that will allow you (or another instance of yourself) to resume work efficiently in a future context window where the conversation history will be replaced with this summary. Your summary should be structured, concise, and actionable. Include:
1. Task Overview
The user's core request and success criteria
Any clarifications or constraints they specified
2. Current State
What has been completed so far
Files created, modified, or analyzed (with paths if relevant)
Key outputs or artifacts produced
3. Important Discoveries
Technical constraints or requirements uncovered
Decisions made and their rationale
Errors encountered and how they were resolved
What approaches were tried that didn't work (and why)
4. Next Steps
Specific actions needed to complete the task
Any blockers or open questions to resolve
Priority order if multiple steps remain
5. Context to Preserve
User preferences or style requirements
Domain-specific details that aren't obvious
Any promises made to the user
Be concise but complete—err on the side of including information that would prevent duplicate work or repeated mistakes. Write in a way that enables immediate resumption of the task.
Wrap your summary in <summary></summary> tags.`;
//# sourceMappingURL=CompactionControl.mjs.map

View File

@@ -1,38 +0,0 @@
/**
* An error that can be thrown from a tool's `run` method to return structured
* content blocks as the error result, rather than just a string message.
*
* When the ToolRunner catches this error, it will use the `content` property
* as the tool result with `is_error: true`.
*
* @example
* ```ts
* const tool = {
* name: 'my_tool',
* run: async (input) => {
* if (somethingWentWrong) {
* throw new ToolError([
* { type: 'text', text: 'Error details here' },
* { type: 'image', source: { type: 'base64', data: '...', media_type: 'image/png' } },
* ]);
* }
* return 'success';
* },
* };
* ```
*/
export class ToolError extends Error {
constructor(content) {
const message = typeof content === 'string' ? content : (content
.map((block) => {
if (block.type === 'text')
return block.text;
return `[${block.type}]`;
})
.join(' '));
super(message);
this.name = 'ToolError';
this.content = content;
}
}
//# sourceMappingURL=ToolError.mjs.map

View File

@@ -1,24 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../core/resource.mjs";
import * as FilesAPI from "./files.mjs";
import { Files, } from "./files.mjs";
import * as ModelsAPI from "./models.mjs";
import { Models } from "./models.mjs";
import * as MessagesAPI from "./messages/messages.mjs";
import { Messages, } from "./messages/messages.mjs";
import * as SkillsAPI from "./skills/skills.mjs";
import { Skills, } from "./skills/skills.mjs";
export class Beta extends APIResource {
constructor() {
super(...arguments);
this.models = new ModelsAPI.Models(this._client);
this.messages = new MessagesAPI.Messages(this._client);
this.files = new FilesAPI.Files(this._client);
this.skills = new SkillsAPI.Skills(this._client);
}
}
Beta.Models = Models;
Beta.Messages = Messages;
Beta.Files = Files;
Beta.Skills = Skills;
//# sourceMappingURL=beta.mjs.map

View File

@@ -1,120 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../core/resource.mjs";
import { Page } from "../../core/pagination.mjs";
import { buildHeaders } from "../../internal/headers.mjs";
import { stainlessHelperHeaderFromFile } from "../../lib/stainless-helper-header.mjs";
import { multipartFormRequestOptions } from "../../internal/uploads.mjs";
import { path } from "../../internal/utils/path.mjs";
export class Files extends APIResource {
/**
* List Files
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const fileMetadata of client.beta.files.list()) {
* // ...
* }
* ```
*/
list(params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/files', (Page), {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Delete File
*
* @example
* ```ts
* const deletedFile = await client.beta.files.delete(
* 'file_id',
* );
* ```
*/
delete(fileID, params = {}, options) {
const { betas } = params ?? {};
return this._client.delete(path `/v1/files/${fileID}`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Download File
*
* @example
* ```ts
* const response = await client.beta.files.download(
* 'file_id',
* );
*
* const content = await response.blob();
* console.log(content);
* ```
*/
download(fileID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/files/${fileID}/content`, {
...options,
headers: buildHeaders([
{
'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString(),
Accept: 'application/binary',
},
options?.headers,
]),
__binaryResponse: true,
});
}
/**
* Get File Metadata
*
* @example
* ```ts
* const fileMetadata =
* await client.beta.files.retrieveMetadata('file_id');
* ```
*/
retrieveMetadata(fileID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/files/${fileID}`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
options?.headers,
]),
});
}
/**
* Upload File
*
* @example
* ```ts
* const fileMetadata = await client.beta.files.upload({
* file: fs.createReadStream('path/to/file'),
* });
* ```
*/
upload(params, options) {
const { betas, ...body } = params;
return this._client.post('/v1/files', multipartFormRequestOptions({
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'files-api-2025-04-14'].toString() },
stainlessHelperHeaderFromFile(body.file),
options?.headers,
]),
}, this._client));
}
}
//# sourceMappingURL=files.mjs.map

View File

@@ -1,200 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../../core/resource.mjs";
import { Page } from "../../../core/pagination.mjs";
import { buildHeaders } from "../../../internal/headers.mjs";
import { JSONLDecoder } from "../../../internal/decoders/jsonl.mjs";
import { AnthropicError } from "../../../error.mjs";
import { path } from "../../../internal/utils/path.mjs";
export class Batches extends APIResource {
/**
* Send a batch of Message creation requests.
*
* The Message Batches API can be used to process multiple Messages API requests at
* once. Once a Message Batch is created, it begins processing immediately. Batches
* can take up to 24 hours to complete.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.create({
* requests: [
* {
* custom_id: 'my-custom-id-1',
* params: {
* max_tokens: 1024,
* messages: [
* { content: 'Hello, world', role: 'user' },
* ],
* model: 'claude-opus-4-6',
* },
* },
* ],
* });
* ```
*/
create(params, options) {
const { betas, ...body } = params;
return this._client.post('/v1/messages/batches?beta=true', {
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* This endpoint is idempotent and can be used to poll for Message Batch
* completion. To access the results of a Message Batch, make a request to the
* `results_url` field in the response.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.retrieve(
* 'message_batch_id',
* );
* ```
*/
retrieve(messageBatchID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/messages/batches/${messageBatchID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* List all Message Batches within a Workspace. Most recently created batches are
* returned first.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const betaMessageBatch of client.beta.messages.batches.list()) {
* // ...
* }
* ```
*/
list(params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/messages/batches?beta=true', (Page), {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Delete a Message Batch.
*
* Message Batches can only be deleted once they've finished processing. If you'd
* like to delete an in-progress batch, you must first cancel it.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaDeletedMessageBatch =
* await client.beta.messages.batches.delete(
* 'message_batch_id',
* );
* ```
*/
delete(messageBatchID, params = {}, options) {
const { betas } = params ?? {};
return this._client.delete(path `/v1/messages/batches/${messageBatchID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Batches may be canceled any time before processing ends. Once cancellation is
* initiated, the batch enters a `canceling` state, at which time the system may
* complete any in-progress, non-interruptible requests before finalizing
* cancellation.
*
* The number of canceled requests is specified in `request_counts`. To determine
* which requests were canceled, check the individual results within the batch.
* Note that cancellation may not result in any canceled requests if they were
* non-interruptible.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatch =
* await client.beta.messages.batches.cancel(
* 'message_batch_id',
* );
* ```
*/
cancel(messageBatchID, params = {}, options) {
const { betas } = params ?? {};
return this._client.post(path `/v1/messages/batches/${messageBatchID}/cancel?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString() },
options?.headers,
]),
});
}
/**
* Streams the results of a Message Batch as a `.jsonl` file.
*
* Each line in the file is a JSON object containing the result of a single request
* in the Message Batch. Results are not guaranteed to be in the same order as
* requests. Use the `custom_id` field to match results to requests.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const betaMessageBatchIndividualResponse =
* await client.beta.messages.batches.results(
* 'message_batch_id',
* );
* ```
*/
async results(messageBatchID, params = {}, options) {
const batch = await this.retrieve(messageBatchID);
if (!batch.results_url) {
throw new AnthropicError(`No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`);
}
const { betas } = params ?? {};
return this._client
.get(batch.results_url, {
...options,
headers: buildHeaders([
{
'anthropic-beta': [...(betas ?? []), 'message-batches-2024-09-24'].toString(),
Accept: 'application/binary',
},
options?.headers,
]),
stream: true,
__binaryResponse: true,
})
._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller));
}
}
//# sourceMappingURL=batches.mjs.map

View File

@@ -1,156 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../../error.mjs";
import { APIResource } from "../../../core/resource.mjs";
import { MODEL_NONSTREAMING_TOKENS } from "../../../internal/constants.mjs";
import { buildHeaders } from "../../../internal/headers.mjs";
import { stainlessHelperHeader } from "../../../lib/stainless-helper-header.mjs";
import { parseBetaMessage, } from "../../../lib/beta-parser.mjs";
import { BetaMessageStream } from "../../../lib/BetaMessageStream.mjs";
import { BetaToolRunner, } from "../../../lib/tools/BetaToolRunner.mjs";
import { ToolError } from "../../../lib/tools/ToolError.mjs";
import * as BatchesAPI from "./batches.mjs";
import { Batches, } from "./batches.mjs";
const DEPRECATED_MODELS = {
'claude-1.3': 'November 6th, 2024',
'claude-1.3-100k': 'November 6th, 2024',
'claude-instant-1.1': 'November 6th, 2024',
'claude-instant-1.1-100k': 'November 6th, 2024',
'claude-instant-1.2': 'November 6th, 2024',
'claude-3-sonnet-20240229': 'July 21st, 2025',
'claude-3-opus-20240229': 'January 5th, 2026',
'claude-2.1': 'July 21st, 2025',
'claude-2.0': 'July 21st, 2025',
'claude-3-7-sonnet-latest': 'February 19th, 2026',
'claude-3-7-sonnet-20250219': 'February 19th, 2026',
};
const MODELS_TO_WARN_WITH_THINKING_ENABLED = ['claude-opus-4-6'];
export class Messages extends APIResource {
constructor() {
super(...arguments);
this.batches = new BatchesAPI.Batches(this._client);
}
create(params, options) {
// Transform deprecated output_format to output_config.format
const modifiedParams = transformOutputFormat(params);
const { betas, ...body } = modifiedParams;
if (body.model in DEPRECATED_MODELS) {
console.warn(`The model '${body.model}' is deprecated and will reach end-of-life on ${DEPRECATED_MODELS[body.model]}\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.`);
}
if (body.model in MODELS_TO_WARN_WITH_THINKING_ENABLED &&
body.thinking &&
body.thinking.type === 'enabled') {
console.warn(`Using Claude with ${body.model} and 'thinking.type=enabled' is deprecated. Use 'thinking.type=adaptive' instead which results in better model performance in our testing: https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking`);
}
let timeout = this._client._options.timeout;
if (!body.stream && timeout == null) {
const maxNonstreamingTokens = MODEL_NONSTREAMING_TOKENS[body.model] ?? undefined;
timeout = this._client.calculateNonstreamingTimeout(body.max_tokens, maxNonstreamingTokens);
}
// Collect helper info from tools and messages
const helperHeader = stainlessHelperHeader(body.tools, body.messages);
return this._client.post('/v1/messages?beta=true', {
body,
timeout: timeout ?? 600000,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
helperHeader,
options?.headers,
]),
stream: modifiedParams.stream ?? false,
});
}
/**
* Send a structured list of input messages with text and/or image content, along with an expected `output_format` and
* the response will be automatically parsed and available in the `parsed_output` property of the message.
*
* @example
* ```ts
* const message = await client.beta.messages.parse({
* model: 'claude-3-5-sonnet-20241022',
* max_tokens: 1024,
* messages: [{ role: 'user', content: 'What is 2+2?' }],
* output_format: zodOutputFormat(z.object({ answer: z.number() }), 'math'),
* });
*
* console.log(message.parsed_output?.answer); // 4
* ```
*/
parse(params, options) {
options = {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(params.betas ?? []), 'structured-outputs-2025-12-15'].toString() },
options?.headers,
]),
};
return this.create(params, options).then((message) => parseBetaMessage(message, params, { logger: this._client.logger ?? console }));
}
/**
* Create a Message stream
*/
stream(body, options) {
return BetaMessageStream.createMessage(this, body, options);
}
/**
* Count the number of tokens in a Message.
*
* The Token Count API can be used to count the number of tokens in a Message,
* including tools, images, and documents, without creating it.
*
* Learn more about token counting in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/token-counting)
*
* @example
* ```ts
* const betaMessageTokensCount =
* await client.beta.messages.countTokens({
* messages: [{ content: 'string', role: 'user' }],
* model: 'claude-opus-4-6',
* });
* ```
*/
countTokens(params, options) {
// Transform deprecated output_format to output_config.format
const modifiedParams = transformOutputFormat(params);
const { betas, ...body } = modifiedParams;
return this._client.post('/v1/messages/count_tokens?beta=true', {
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'token-counting-2024-11-01'].toString() },
options?.headers,
]),
});
}
toolRunner(body, options) {
return new BetaToolRunner(this._client, body, options);
}
}
/**
* Transform deprecated output_format to output_config.format
* Returns a modified copy of the params without mutating the original
*/
function transformOutputFormat(params) {
if (!params.output_format) {
return params;
}
if (params.output_config?.format) {
throw new AnthropicError('Both output_format and output_config.format were provided. ' +
'Please use only output_config.format (output_format is deprecated).');
}
const { output_format, ...rest } = params;
return {
...rest,
output_config: {
...params.output_config,
format: output_format,
},
};
}
export { BetaToolRunner } from "../../../lib/tools/BetaToolRunner.mjs";
export { ToolError } from "../../../lib/tools/ToolError.mjs";
Messages.Batches = Batches;
Messages.BetaToolRunner = BetaToolRunner;
Messages.ToolError = ToolError;
//# sourceMappingURL=messages.mjs.map

View File

@@ -1,56 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../core/resource.mjs";
import { Page } from "../../core/pagination.mjs";
import { buildHeaders } from "../../internal/headers.mjs";
import { path } from "../../internal/utils/path.mjs";
export class Models extends APIResource {
/**
* Get a specific model.
*
* The Models API response can be used to determine information about a specific
* model or resolve a model alias to a model ID.
*
* @example
* ```ts
* const betaModelInfo = await client.beta.models.retrieve(
* 'model_id',
* );
* ```
*/
retrieve(modelID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/models/${modelID}?beta=true`, {
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
/**
* List available models.
*
* The Models API response can be used to determine which models are available for
* use in the API. More recently released models are listed first.
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const betaModelInfo of client.beta.models.list()) {
* // ...
* }
* ```
*/
list(params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/models?beta=true', (Page), {
query,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
}
//# sourceMappingURL=models.mjs.map

View File

@@ -1,93 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../../core/resource.mjs";
import * as VersionsAPI from "./versions.mjs";
import { Versions, } from "./versions.mjs";
import { PageCursor } from "../../../core/pagination.mjs";
import { buildHeaders } from "../../../internal/headers.mjs";
import { multipartFormRequestOptions } from "../../../internal/uploads.mjs";
import { path } from "../../../internal/utils/path.mjs";
export class Skills extends APIResource {
constructor() {
super(...arguments);
this.versions = new VersionsAPI.Versions(this._client);
}
/**
* Create Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.create();
* ```
*/
create(params = {}, options) {
const { betas, ...body } = params ?? {};
return this._client.post('/v1/skills?beta=true', multipartFormRequestOptions({
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
}, this._client, false));
}
/**
* Get Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.retrieve('skill_id');
* ```
*/
retrieve(skillID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/skills/${skillID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* List Skills
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const skillListResponse of client.beta.skills.list()) {
* // ...
* }
* ```
*/
list(params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/skills?beta=true', (PageCursor), {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* Delete Skill
*
* @example
* ```ts
* const skill = await client.beta.skills.delete('skill_id');
* ```
*/
delete(skillID, params = {}, options) {
const { betas } = params ?? {};
return this._client.delete(path `/v1/skills/${skillID}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
}
Skills.Versions = Versions;
//# sourceMappingURL=skills.mjs.map

View File

@@ -1,96 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../../core/resource.mjs";
import { PageCursor } from "../../../core/pagination.mjs";
import { buildHeaders } from "../../../internal/headers.mjs";
import { multipartFormRequestOptions } from "../../../internal/uploads.mjs";
import { path } from "../../../internal/utils/path.mjs";
export class Versions extends APIResource {
/**
* Create Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.create(
* 'skill_id',
* );
* ```
*/
create(skillID, params = {}, options) {
const { betas, ...body } = params ?? {};
return this._client.post(path `/v1/skills/${skillID}/versions?beta=true`, multipartFormRequestOptions({
body,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
}, this._client));
}
/**
* Get Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.retrieve(
* 'version',
* { skill_id: 'skill_id' },
* );
* ```
*/
retrieve(version, params, options) {
const { skill_id, betas } = params;
return this._client.get(path `/v1/skills/${skill_id}/versions/${version}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* List Skill Versions
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const versionListResponse of client.beta.skills.versions.list(
* 'skill_id',
* )) {
* // ...
* }
* ```
*/
list(skillID, params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList(path `/v1/skills/${skillID}/versions?beta=true`, (PageCursor), {
query,
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
/**
* Delete Skill Version
*
* @example
* ```ts
* const version = await client.beta.skills.versions.delete(
* 'version',
* { skill_id: 'skill_id' },
* );
* ```
*/
delete(version, params, options) {
const { skill_id, betas } = params;
return this._client.delete(path `/v1/skills/${skill_id}/versions/${version}?beta=true`, {
...options,
headers: buildHeaders([
{ 'anthropic-beta': [...(betas ?? []), 'skills-2025-10-02'].toString() },
options?.headers,
]),
});
}
}
//# sourceMappingURL=versions.mjs.map

View File

@@ -1,19 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../core/resource.mjs";
import { buildHeaders } from "../internal/headers.mjs";
export class Completions extends APIResource {
create(params, options) {
const { betas, ...body } = params;
return this._client.post('/v1/complete', {
body,
timeout: this._client._options.timeout ?? 600000,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
stream: params.stream ?? false,
});
}
}
//# sourceMappingURL=completions.mjs.map

View File

@@ -1,7 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
export * from "./shared.mjs";
export { Beta, } from "./beta/beta.mjs";
export { Completions, } from "./completions.mjs";
export { Messages, } from "./messages/messages.mjs";
export { Models, } from "./models.mjs";
//# sourceMappingURL=index.mjs.map

View File

@@ -1,149 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../core/resource.mjs";
import { Page } from "../../core/pagination.mjs";
import { buildHeaders } from "../../internal/headers.mjs";
import { JSONLDecoder } from "../../internal/decoders/jsonl.mjs";
import { AnthropicError } from "../../error.mjs";
import { path } from "../../internal/utils/path.mjs";
export class Batches extends APIResource {
/**
* Send a batch of Message creation requests.
*
* The Message Batches API can be used to process multiple Messages API requests at
* once. Once a Message Batch is created, it begins processing immediately. Batches
* can take up to 24 hours to complete.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.create({
* requests: [
* {
* custom_id: 'my-custom-id-1',
* params: {
* max_tokens: 1024,
* messages: [
* { content: 'Hello, world', role: 'user' },
* ],
* model: 'claude-opus-4-6',
* },
* },
* ],
* });
* ```
*/
create(body, options) {
return this._client.post('/v1/messages/batches', { body, ...options });
}
/**
* This endpoint is idempotent and can be used to poll for Message Batch
* completion. To access the results of a Message Batch, make a request to the
* `results_url` field in the response.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.retrieve(
* 'message_batch_id',
* );
* ```
*/
retrieve(messageBatchID, options) {
return this._client.get(path `/v1/messages/batches/${messageBatchID}`, options);
}
/**
* List all Message Batches within a Workspace. Most recently created batches are
* returned first.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* // Automatically fetches more pages as needed.
* for await (const messageBatch of client.messages.batches.list()) {
* // ...
* }
* ```
*/
list(query = {}, options) {
return this._client.getAPIList('/v1/messages/batches', (Page), { query, ...options });
}
/**
* Delete a Message Batch.
*
* Message Batches can only be deleted once they've finished processing. If you'd
* like to delete an in-progress batch, you must first cancel it.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const deletedMessageBatch =
* await client.messages.batches.delete('message_batch_id');
* ```
*/
delete(messageBatchID, options) {
return this._client.delete(path `/v1/messages/batches/${messageBatchID}`, options);
}
/**
* Batches may be canceled any time before processing ends. Once cancellation is
* initiated, the batch enters a `canceling` state, at which time the system may
* complete any in-progress, non-interruptible requests before finalizing
* cancellation.
*
* The number of canceled requests is specified in `request_counts`. To determine
* which requests were canceled, check the individual results within the batch.
* Note that cancellation may not result in any canceled requests if they were
* non-interruptible.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatch = await client.messages.batches.cancel(
* 'message_batch_id',
* );
* ```
*/
cancel(messageBatchID, options) {
return this._client.post(path `/v1/messages/batches/${messageBatchID}/cancel`, options);
}
/**
* Streams the results of a Message Batch as a `.jsonl` file.
*
* Each line in the file is a JSON object containing the result of a single request
* in the Message Batch. Results are not guaranteed to be in the same order as
* requests. Use the `custom_id` field to match results to requests.
*
* Learn more about the Message Batches API in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/batch-processing)
*
* @example
* ```ts
* const messageBatchIndividualResponse =
* await client.messages.batches.results('message_batch_id');
* ```
*/
async results(messageBatchID, options) {
const batch = await this.retrieve(messageBatchID);
if (!batch.results_url) {
throw new AnthropicError(`No batch \`results_url\`; Has it finished processing? ${batch.processing_status} - ${batch.id}`);
}
return this._client
.get(batch.results_url, {
...options,
headers: buildHeaders([{ Accept: 'application/binary' }, options?.headers]),
stream: true,
__binaryResponse: true,
})
._thenUnwrap((_, props) => JSONLDecoder.fromResponse(props.response, props.controller));
}
}
//# sourceMappingURL=batches.mjs.map

View File

@@ -1,123 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../../core/resource.mjs";
import { buildHeaders } from "../../internal/headers.mjs";
import { stainlessHelperHeader } from "../../lib/stainless-helper-header.mjs";
import { MessageStream } from "../../lib/MessageStream.mjs";
import { parseMessage, } from "../../lib/parser.mjs";
import * as BatchesAPI from "./batches.mjs";
import { Batches, } from "./batches.mjs";
import { MODEL_NONSTREAMING_TOKENS } from "../../internal/constants.mjs";
export class Messages extends APIResource {
constructor() {
super(...arguments);
this.batches = new BatchesAPI.Batches(this._client);
}
create(body, options) {
if (body.model in DEPRECATED_MODELS) {
console.warn(`The model '${body.model}' is deprecated and will reach end-of-life on ${DEPRECATED_MODELS[body.model]}\nPlease migrate to a newer model. Visit https://docs.anthropic.com/en/docs/resources/model-deprecations for more information.`);
}
if (body.model in MODELS_TO_WARN_WITH_THINKING_ENABLED &&
body.thinking &&
body.thinking.type === 'enabled') {
console.warn(`Using Claude with ${body.model} and 'thinking.type=enabled' is deprecated. Use 'thinking.type=adaptive' instead which results in better model performance in our testing: https://platform.claude.com/docs/en/build-with-claude/adaptive-thinking`);
}
let timeout = this._client._options.timeout;
if (!body.stream && timeout == null) {
const maxNonstreamingTokens = MODEL_NONSTREAMING_TOKENS[body.model] ?? undefined;
timeout = this._client.calculateNonstreamingTimeout(body.max_tokens, maxNonstreamingTokens);
}
// Collect helper info from tools and messages
const helperHeader = stainlessHelperHeader(body.tools, body.messages);
return this._client.post('/v1/messages', {
body,
timeout: timeout ?? 600000,
...options,
headers: buildHeaders([helperHeader, options?.headers]),
stream: body.stream ?? false,
});
}
/**
* Send a structured list of input messages with text and/or image content, along with an expected `output_config.format` and
* the response will be automatically parsed and available in the `parsed_output` property of the message.
*
* @example
* ```ts
* const message = await client.messages.parse({
* model: 'claude-sonnet-4-5-20250929',
* max_tokens: 1024,
* messages: [{ role: 'user', content: 'What is 2+2?' }],
* output_config: {
* format: zodOutputFormat(z.object({ answer: z.number() })),
* },
* });
*
* console.log(message.parsed_output?.answer); // 4
* ```
*/
parse(params, options) {
return this.create(params, options).then((message) => parseMessage(message, params, { logger: this._client.logger ?? console }));
}
/**
* Create a Message stream.
*
* If `output_config.format` is provided with a parseable format (like `zodOutputFormat()`),
* the final message will include a `parsed_output` property with the parsed content.
*
* @example
* ```ts
* const stream = client.messages.stream({
* model: 'claude-sonnet-4-5-20250929',
* max_tokens: 1024,
* messages: [{ role: 'user', content: 'What is 2+2?' }],
* output_config: {
* format: zodOutputFormat(z.object({ answer: z.number() })),
* },
* });
*
* const message = await stream.finalMessage();
* console.log(message.parsed_output?.answer); // 4
* ```
*/
stream(body, options) {
return MessageStream.createMessage(this, body, options, { logger: this._client.logger ?? console });
}
/**
* Count the number of tokens in a Message.
*
* The Token Count API can be used to count the number of tokens in a Message,
* including tools, images, and documents, without creating it.
*
* Learn more about token counting in our
* [user guide](https://docs.claude.com/en/docs/build-with-claude/token-counting)
*
* @example
* ```ts
* const messageTokensCount =
* await client.messages.countTokens({
* messages: [{ content: 'string', role: 'user' }],
* model: 'claude-opus-4-6',
* });
* ```
*/
countTokens(body, options) {
return this._client.post('/v1/messages/count_tokens', { body, ...options });
}
}
const DEPRECATED_MODELS = {
'claude-1.3': 'November 6th, 2024',
'claude-1.3-100k': 'November 6th, 2024',
'claude-instant-1.1': 'November 6th, 2024',
'claude-instant-1.1-100k': 'November 6th, 2024',
'claude-instant-1.2': 'November 6th, 2024',
'claude-3-sonnet-20240229': 'July 21st, 2025',
'claude-3-opus-20240229': 'January 5th, 2026',
'claude-2.1': 'July 21st, 2025',
'claude-2.0': 'July 21st, 2025',
'claude-3-7-sonnet-latest': 'February 19th, 2026',
'claude-3-7-sonnet-20250219': 'February 19th, 2026',
'claude-3-5-haiku-latest': 'February 19th, 2026',
'claude-3-5-haiku-20241022': 'February 19th, 2026',
};
const MODELS_TO_WARN_WITH_THINKING_ENABLED = ['claude-opus-4-6'];
Messages.Batches = Batches;
//# sourceMappingURL=messages.mjs.map

View File

@@ -1,41 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { APIResource } from "../core/resource.mjs";
import { Page } from "../core/pagination.mjs";
import { buildHeaders } from "../internal/headers.mjs";
import { path } from "../internal/utils/path.mjs";
export class Models extends APIResource {
/**
* Get a specific model.
*
* The Models API response can be used to determine information about a specific
* model or resolve a model alias to a model ID.
*/
retrieve(modelID, params = {}, options) {
const { betas } = params ?? {};
return this._client.get(path `/v1/models/${modelID}`, {
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
/**
* List available models.
*
* The Models API response can be used to determine which models are available for
* use in the API. More recently released models are listed first.
*/
list(params = {}, options) {
const { betas, ...query } = params ?? {};
return this._client.getAPIList('/v1/models', (Page), {
query,
...options,
headers: buildHeaders([
{ ...(betas?.toString() != null ? { 'anthropic-beta': betas?.toString() } : undefined) },
options?.headers,
]),
});
}
}
//# sourceMappingURL=models.mjs.map

View File

@@ -1,2 +0,0 @@
export * from "./core/streaming.mjs";
//# sourceMappingURL=streaming.mjs.map

View File

@@ -1,2 +0,0 @@
export const VERSION = '0.74.0'; // x-release-please-version
//# sourceMappingURL=version.mjs.map

View File

@@ -1,114 +0,0 @@
import { BaseAnthropic } from '@anthropic-ai/sdk/client';
import * as Resources from '@anthropic-ai/sdk/resources/index';
import { GoogleAuth } from 'google-auth-library';
import { readEnv } from "./internal/utils/env.mjs";
import { isObj } from "./internal/utils/values.mjs";
import { buildHeaders } from "./internal/headers.mjs";
export { BaseAnthropic } from '@anthropic-ai/sdk/client';
const DEFAULT_VERSION = 'vertex-2023-10-16';
const MODEL_ENDPOINTS = new Set(['/v1/messages', '/v1/messages?beta=true']);
export class AnthropicVertex extends BaseAnthropic {
/**
* API Client for interfacing with the Anthropic Vertex API.
*
* @param {string | null} opts.accessToken
* @param {string | null} opts.projectId
* @param {GoogleAuth} opts.googleAuth - Override the default google auth config
* @param {AuthClient} opts.authClient - Provide a pre-configured AuthClient instance (alternative to googleAuth)
* @param {string | null} [opts.region=process.env['CLOUD_ML_REGION']] - The region to use for the API. Use 'global' for global endpoint. [More details here](https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations).
* @param {string} [opts.baseURL=process.env['ANTHROPIC_VERTEX__BASE_URL'] ?? https://${region}-aiplatform.googleapis.com/v1] - Override the default base URL for the API.
* @param {number} [opts.timeout=10 minutes] - The maximum amount of time (in milliseconds) the client will wait for a response before timing out.
* @param {MergedRequestInit} [opts.fetchOptions] - Additional `RequestInit` options to be passed to `fetch` calls.
* @param {Fetch} [opts.fetch] - Specify a custom `fetch` function implementation.
* @param {number} [opts.maxRetries=2] - The maximum number of times the client will retry a request.
* @param {HeadersLike} opts.defaultHeaders - Default headers to include with every request to the API.
* @param {Record<string, string | undefined>} opts.defaultQuery - Default query parameters to include with every request to the API.
* @param {boolean} [opts.dangerouslyAllowBrowser=false] - By default, client-side use of this library is not allowed, as it risks exposing your secret API credentials to attackers.
*/
constructor({ baseURL = readEnv('ANTHROPIC_VERTEX_BASE_URL'), region = readEnv('CLOUD_ML_REGION') ?? null, projectId = readEnv('ANTHROPIC_VERTEX_PROJECT_ID') ?? null, ...opts } = {}) {
if (!region) {
throw new Error('No region was given. The client should be instantiated with the `region` option or the `CLOUD_ML_REGION` environment variable should be set.');
}
super({
baseURL: baseURL ||
(region === 'global' ?
'https://aiplatform.googleapis.com/v1'
: `https://${region}-aiplatform.googleapis.com/v1`),
...opts,
});
this.messages = makeMessagesResource(this);
this.beta = makeBetaResource(this);
this.region = region;
this.projectId = projectId;
this.accessToken = opts.accessToken ?? null;
if (opts.authClient && opts.googleAuth) {
throw new Error('You cannot provide both `authClient` and `googleAuth`. Please provide only one of them.');
}
else if (opts.authClient) {
this._authClientPromise = Promise.resolve(opts.authClient);
}
else {
this._auth =
opts.googleAuth ?? new GoogleAuth({ scopes: 'https://www.googleapis.com/auth/cloud-platform' });
this._authClientPromise = this._auth.getClient();
}
}
validateHeaders() {
// auth validation is handled in prepareOptions since it needs to be async
}
async prepareOptions(options) {
const authClient = await this._authClientPromise;
const authHeaders = await authClient.getRequestHeaders();
const projectId = authClient.projectId ?? authHeaders['x-goog-user-project'];
if (!this.projectId && projectId) {
this.projectId = projectId;
}
options.headers = buildHeaders([authHeaders, options.headers]);
}
async buildRequest(options) {
if (isObj(options.body)) {
// create a shallow copy of the request body so that code that mutates it later
// doesn't mutate the original user-provided object
options.body = { ...options.body };
}
if (isObj(options.body)) {
if (!options.body['anthropic_version']) {
options.body['anthropic_version'] = DEFAULT_VERSION;
}
}
if (MODEL_ENDPOINTS.has(options.path) && options.method === 'post') {
if (!this.projectId) {
throw new Error('No projectId was given and it could not be resolved from credentials. The client should be instantiated with the `projectId` option or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set.');
}
if (!isObj(options.body)) {
throw new Error('Expected request body to be an object for post /v1/messages');
}
const model = options.body['model'];
options.body['model'] = undefined;
const stream = options.body['stream'] ?? false;
const specifier = stream ? 'streamRawPredict' : 'rawPredict';
options.path = `/projects/${this.projectId}/locations/${this.region}/publishers/anthropic/models/${model}:${specifier}`;
}
if (options.path === '/v1/messages/count_tokens' ||
(options.path == '/v1/messages/count_tokens?beta=true' && options.method === 'post')) {
if (!this.projectId) {
throw new Error('No projectId was given and it could not be resolved from credentials. The client should be instantiated with the `projectId` option or the `ANTHROPIC_VERTEX_PROJECT_ID` environment variable should be set.');
}
options.path = `/projects/${this.projectId}/locations/${this.region}/publishers/anthropic/models/count-tokens:rawPredict`;
}
return super.buildRequest(options);
}
}
function makeMessagesResource(client) {
const resource = new Resources.Messages(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.batches;
return resource;
}
function makeBetaResource(client) {
const resource = new Resources.Beta(client);
// @ts-expect-error we're deleting non-optional properties
delete resource.messages.batches;
return resource;
}
//# sourceMappingURL=client.mjs.map

View File

@@ -1,2 +0,0 @@
export * from '@anthropic-ai/sdk/core/error';
//# sourceMappingURL=error.mjs.map

View File

@@ -1,3 +0,0 @@
export * from "./client.mjs";
export { AnthropicVertex as default } from "./client.mjs";
//# sourceMappingURL=index.mjs.map

View File

@@ -1,74 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { isReadonlyArray } from "./utils/values.mjs";
const brand_privateNullableHeaders = Symbol.for('brand.privateNullableHeaders');
function* iterateHeaders(headers) {
if (!headers)
return;
if (brand_privateNullableHeaders in headers) {
const { values, nulls } = headers;
yield* values.entries();
for (const name of nulls) {
yield [name, null];
}
return;
}
let shouldClear = false;
let iter;
if (headers instanceof Headers) {
iter = headers.entries();
}
else if (isReadonlyArray(headers)) {
iter = headers;
}
else {
shouldClear = true;
iter = Object.entries(headers ?? {});
}
for (let row of iter) {
const name = row[0];
if (typeof name !== 'string')
throw new TypeError('expected header name to be a string');
const values = isReadonlyArray(row[1]) ? row[1] : [row[1]];
let didClear = false;
for (const value of values) {
if (value === undefined)
continue;
// Objects keys always overwrite older headers, they never append.
// Yield a null to clear the header before adding the new values.
if (shouldClear && !didClear) {
didClear = true;
yield [name, null];
}
yield [name, value];
}
}
}
export const buildHeaders = (newHeaders) => {
const targetHeaders = new Headers();
const nullHeaders = new Set();
for (const headers of newHeaders) {
const seenHeaders = new Set();
for (const [name, value] of iterateHeaders(headers)) {
const lowerName = name.toLowerCase();
if (!seenHeaders.has(lowerName)) {
targetHeaders.delete(name);
seenHeaders.add(lowerName);
}
if (value === null) {
targetHeaders.delete(name);
nullHeaders.add(lowerName);
}
else {
targetHeaders.append(name, value);
nullHeaders.delete(lowerName);
}
}
}
return { [brand_privateNullableHeaders]: true, values: targetHeaders, nulls: nullHeaders };
};
export const isEmptyHeaders = (headers) => {
for (const _ of iterateHeaders(headers))
return false;
return true;
};
//# sourceMappingURL=headers.mjs.map

View File

@@ -1,18 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
/**
* Read an environment variable.
*
* Trims beginning and trailing whitespace.
*
* Will return undefined if the environment variable doesn't exist or cannot be accessed.
*/
export const readEnv = (env) => {
if (typeof globalThis.process !== 'undefined') {
return globalThis.process.env?.[env]?.trim() ?? undefined;
}
if (typeof globalThis.Deno !== 'undefined') {
return globalThis.Deno.env?.get?.(env)?.trim();
}
return undefined;
};
//# sourceMappingURL=env.mjs.map

View File

@@ -1,94 +0,0 @@
// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
import { AnthropicError } from "../../core/error.mjs";
// https://url.spec.whatwg.org/#url-scheme-string
const startsWithSchemeRegexp = /^[a-z][a-z0-9+.-]*:/i;
export const isAbsoluteURL = (url) => {
return startsWithSchemeRegexp.test(url);
};
export let isArray = (val) => ((isArray = Array.isArray), isArray(val));
export let isReadonlyArray = isArray;
/** Returns an object if the given value isn't an object, otherwise returns as-is */
export function maybeObj(x) {
if (typeof x !== 'object') {
return {};
}
return x ?? {};
}
// https://stackoverflow.com/a/34491287
export function isEmptyObj(obj) {
if (!obj)
return true;
for (const _k in obj)
return false;
return true;
}
// https://eslint.org/docs/latest/rules/no-prototype-builtins
export function hasOwn(obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key);
}
export function isObj(obj) {
return obj != null && typeof obj === 'object' && !Array.isArray(obj);
}
export const ensurePresent = (value) => {
if (value == null) {
throw new AnthropicError(`Expected a value to be given but received ${value} instead.`);
}
return value;
};
export const validatePositiveInteger = (name, n) => {
if (typeof n !== 'number' || !Number.isInteger(n)) {
throw new AnthropicError(`${name} must be an integer`);
}
if (n < 0) {
throw new AnthropicError(`${name} must be a positive integer`);
}
return n;
};
export const coerceInteger = (value) => {
if (typeof value === 'number')
return Math.round(value);
if (typeof value === 'string')
return parseInt(value, 10);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceFloat = (value) => {
if (typeof value === 'number')
return value;
if (typeof value === 'string')
return parseFloat(value);
throw new AnthropicError(`Could not coerce ${value} (type: ${typeof value}) into a number`);
};
export const coerceBoolean = (value) => {
if (typeof value === 'boolean')
return value;
if (typeof value === 'string')
return value === 'true';
return Boolean(value);
};
export const maybeCoerceInteger = (value) => {
if (value == null) {
return undefined;
}
return coerceInteger(value);
};
export const maybeCoerceFloat = (value) => {
if (value == null) {
return undefined;
}
return coerceFloat(value);
};
export const maybeCoerceBoolean = (value) => {
if (value == null) {
return undefined;
}
return coerceBoolean(value);
};
export const safeJSON = (text) => {
try {
return JSON.parse(text);
}
catch (err) {
return undefined;
}
};
//# sourceMappingURL=values.mjs.map

View File

@@ -1,31 +0,0 @@
"use strict";
// Copyright Amazon.com Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
Object.defineProperty(exports, "__esModule", { value: true });
exports.AwsCrc32 = void 0;
var tslib_1 = require("tslib");
var util_1 = require("@aws-crypto/util");
var index_1 = require("./index");
var AwsCrc32 = /** @class */ (function () {
function AwsCrc32() {
this.crc32 = new index_1.Crc32();
}
AwsCrc32.prototype.update = function (toHash) {
if ((0, util_1.isEmptyData)(toHash))
return;
this.crc32.update((0, util_1.convertToBuffer)(toHash));
};
AwsCrc32.prototype.digest = function () {
return tslib_1.__awaiter(this, void 0, void 0, function () {
return tslib_1.__generator(this, function (_a) {
return [2 /*return*/, (0, util_1.numToUint8)(this.crc32.digest())];
});
});
};
AwsCrc32.prototype.reset = function () {
this.crc32 = new index_1.Crc32();
};
return AwsCrc32;
}());
exports.AwsCrc32 = AwsCrc32;
//# sourceMappingURL=aws_crc32.js.map

Some files were not shown because too many files have changed in this diff Show More