@anthropic-ai/sdk
Version:
The official TypeScript library for the Anthropic API
96 lines (84 loc) • 3.49 kB
text/typescript
import { AnthropicError } from '../../core/error';
/**
* 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: string) {
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: readonly string[], ...params: readonly unknown[]): string {
// 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 as any).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`,
});
}
if (!postPath && encoded === '') {
invalidSegments.push({
start: previousValue.length + currentValue.length,
length: 0,
error: 'Value "" can\'t be safely passed as a 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) {
const marks: string[] = [];
for (const segment of invalidSegments) {
for (let i = segment.start; i < segment.start + Math.max(1, segment.length); i++) {
marks[i] = '^';
}
}
const underline = Array.from(marks, (mark) => mark ?? ' ').join('');
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 that none before any ? or # is empty and none introduces an unsafe /./ or /../ path segment.
*/
export const path = /* @__PURE__ */ createPathTagFunction(encodeURIPath);