@anthropic-ai/foundry-sdk 0.4.0 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/client.d.mts +1 -0
- package/client.d.mts.map +1 -1
- package/client.d.ts +1 -0
- package/client.d.ts.map +1 -1
- package/client.js +4 -0
- package/client.js.map +1 -1
- package/client.mjs +4 -0
- package/client.mjs.map +1 -1
- package/core/error.d.mts.map +1 -1
- package/core/error.d.ts.map +1 -1
- package/core/error.mjs.map +1 -1
- package/core/middleware.d.mts.map +1 -1
- package/core/middleware.d.ts.map +1 -1
- package/core/middleware.mjs.map +1 -1
- package/core/pagination.d.mts.map +1 -1
- package/core/pagination.d.ts.map +1 -1
- package/core/pagination.mjs.map +1 -1
- package/core/streaming.d.mts.map +1 -1
- package/core/streaming.d.ts.map +1 -1
- package/core/streaming.mjs.map +1 -1
- package/index.d.mts.map +1 -1
- package/index.d.ts.map +1 -1
- package/index.mjs.map +1 -1
- package/internal/constants.d.mts.map +1 -1
- package/internal/constants.d.ts.map +1 -1
- package/internal/constants.js +0 -5
- package/internal/constants.js.map +1 -1
- package/internal/constants.mjs +0 -5
- package/internal/constants.mjs.map +1 -1
- package/internal/decoders/jsonl.d.mts.map +1 -1
- package/internal/decoders/jsonl.d.ts.map +1 -1
- package/internal/decoders/jsonl.mjs.map +1 -1
- package/internal/decoders/line.mjs.map +1 -1
- package/internal/headers.d.mts +7 -0
- package/internal/headers.d.mts.map +1 -1
- package/internal/headers.d.ts +7 -0
- package/internal/headers.d.ts.map +1 -1
- package/internal/headers.js +45 -4
- package/internal/headers.js.map +1 -1
- package/internal/headers.mjs +43 -3
- package/internal/headers.mjs.map +1 -1
- package/internal/parse.d.mts.map +1 -1
- package/internal/parse.d.ts.map +1 -1
- package/internal/parse.js +10 -1
- package/internal/parse.js.map +1 -1
- package/internal/parse.mjs +10 -1
- package/internal/parse.mjs.map +1 -1
- package/internal/qs/formats.d.mts.map +1 -1
- package/internal/qs/formats.d.ts.map +1 -1
- package/internal/qs/index.d.mts +2 -2
- package/internal/qs/index.d.mts.map +1 -1
- package/internal/qs/index.d.ts +2 -2
- package/internal/qs/index.d.ts.map +1 -1
- package/internal/qs/index.mjs.map +1 -1
- package/internal/qs/stringify.d.mts.map +1 -1
- package/internal/qs/stringify.d.ts.map +1 -1
- package/internal/qs/stringify.mjs.map +1 -1
- package/internal/qs/utils.d.mts.map +1 -1
- package/internal/qs/utils.d.ts.map +1 -1
- package/internal/qs/utils.mjs.map +1 -1
- package/internal/request-options.d.mts.map +1 -1
- package/internal/request-options.d.ts.map +1 -1
- package/internal/request-signal.d.mts +4 -0
- package/internal/request-signal.d.mts.map +1 -0
- package/internal/request-signal.d.ts +4 -0
- package/internal/request-signal.d.ts.map +1 -0
- package/internal/request-signal.js +50 -0
- package/internal/request-signal.js.map +1 -0
- package/internal/request-signal.mjs +45 -0
- package/internal/request-signal.mjs.map +1 -0
- package/internal/shims.d.mts +6 -0
- package/internal/shims.d.mts.map +1 -1
- package/internal/shims.d.ts +6 -0
- package/internal/shims.d.ts.map +1 -1
- package/internal/stainless-helper-header.d.mts +60 -0
- package/internal/stainless-helper-header.d.mts.map +1 -0
- package/internal/stainless-helper-header.d.ts +60 -0
- package/internal/stainless-helper-header.d.ts.map +1 -0
- package/internal/stainless-helper-header.js +91 -0
- package/internal/stainless-helper-header.js.map +1 -0
- package/internal/stainless-helper-header.mjs +83 -0
- package/internal/stainless-helper-header.mjs.map +1 -0
- package/internal/to-file.d.mts.map +1 -1
- package/internal/to-file.d.ts.map +1 -1
- package/internal/to-file.mjs.map +1 -1
- package/internal/types.d.mts +4 -4
- package/internal/types.d.mts.map +1 -1
- package/internal/types.d.ts +4 -4
- package/internal/types.d.ts.map +1 -1
- package/internal/uploads.d.mts.map +1 -1
- package/internal/uploads.d.ts.map +1 -1
- package/internal/uploads.mjs.map +1 -1
- package/internal/utils/backoff.mjs.map +1 -1
- package/internal/utils/base64.mjs.map +1 -1
- package/internal/utils/log.d.mts.map +1 -1
- package/internal/utils/log.d.ts.map +1 -1
- package/internal/utils/log.mjs.map +1 -1
- package/internal/utils/path.mjs.map +1 -1
- package/internal/utils/query.mjs.map +1 -1
- package/internal/utils/values.mjs.map +1 -1
- package/internal/utils.d.mts.map +1 -1
- package/internal/utils.d.ts.map +1 -1
- package/internal/utils.mjs.map +1 -1
- package/package.json +2 -2
- package/src/client.ts +5 -0
- package/src/internal/constants.ts +0 -5
- package/src/internal/headers.ts +47 -4
- package/src/internal/parse.ts +10 -1
- package/src/internal/request-signal.ts +68 -0
- package/src/internal/stainless-helper-header.ts +122 -0
package/src/internal/headers.ts
CHANGED
|
@@ -29,7 +29,9 @@ export type NullableHeaders = {
|
|
|
29
29
|
nulls: Set<string>;
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
function* iterateHeaders(
|
|
32
|
+
function* iterateHeaders(
|
|
33
|
+
headers: HeadersLike,
|
|
34
|
+
): IterableIterator<readonly [string, string | null | ClearSentinel]> {
|
|
33
35
|
if (!headers) return;
|
|
34
36
|
|
|
35
37
|
if (brand_privateNullableHeaders in headers) {
|
|
@@ -60,16 +62,43 @@ function* iterateHeaders(headers: HeadersLike): IterableIterator<readonly [strin
|
|
|
60
62
|
if (value === undefined) continue;
|
|
61
63
|
|
|
62
64
|
// Objects keys always overwrite older headers, they never append.
|
|
63
|
-
// Yield
|
|
65
|
+
// Yield the clear sentinel before adding the new values, so the
|
|
66
|
+
// consumer can tell this synthetic "clear-before-set" apart from a
|
|
67
|
+
// user's explicit `null` (= remove).
|
|
64
68
|
if (shouldClear && !didClear) {
|
|
65
69
|
didClear = true;
|
|
66
|
-
yield [name,
|
|
70
|
+
yield [name, clearSentinel];
|
|
67
71
|
}
|
|
68
72
|
yield [name, value];
|
|
69
73
|
}
|
|
70
74
|
}
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/** Distinguishes iterateHeaders' synthetic clear-before-set from a user `null`. */
|
|
78
|
+
const clearSentinel = Symbol('clear');
|
|
79
|
+
type ClearSentinel = typeof clearSentinel;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Headers whose values accumulate across {@link buildHeaders} sources instead
|
|
83
|
+
* of the later source's value replacing the earlier one. Values are
|
|
84
|
+
* comma-appended (deduplicated, order-preserving) into a single header line.
|
|
85
|
+
*/
|
|
86
|
+
export const APPEND_HEADERS: ReadonlySet<string> = new Set(['x-stainless-helper']);
|
|
87
|
+
|
|
88
|
+
export const appendHeaderValue = (existing: string | null, addition: string): string => {
|
|
89
|
+
const tokens =
|
|
90
|
+
existing ?
|
|
91
|
+
existing
|
|
92
|
+
.split(',')
|
|
93
|
+
.map((t) => t.trim())
|
|
94
|
+
.filter(Boolean)
|
|
95
|
+
: [];
|
|
96
|
+
for (const tok of addition.split(',').map((t) => t.trim())) {
|
|
97
|
+
if (tok && !tokens.includes(tok)) tokens.push(tok);
|
|
98
|
+
}
|
|
99
|
+
return tokens.join(', ');
|
|
100
|
+
};
|
|
101
|
+
|
|
73
102
|
export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
|
|
74
103
|
const targetHeaders = new Headers();
|
|
75
104
|
const nullHeaders = new Set<string>();
|
|
@@ -77,9 +106,23 @@ export const buildHeaders = (newHeaders: HeadersLike[]): NullableHeaders => {
|
|
|
77
106
|
const seenHeaders = new Set<string>();
|
|
78
107
|
for (const [name, value] of iterateHeaders(headers)) {
|
|
79
108
|
const lowerName = name.toLowerCase();
|
|
80
|
-
if (
|
|
109
|
+
if (APPEND_HEADERS.has(lowerName)) {
|
|
110
|
+
// Accumulating headers ignore the synthetic clear-before-set; an
|
|
111
|
+
// explicit `null` (any source shape) is honored as removal.
|
|
112
|
+
if (value === clearSentinel) continue;
|
|
113
|
+
if (value === null) {
|
|
114
|
+
targetHeaders.delete(name);
|
|
115
|
+
nullHeaders.add(lowerName);
|
|
116
|
+
} else {
|
|
117
|
+
targetHeaders.set(name, appendHeaderValue(targetHeaders.get(name), value));
|
|
118
|
+
nullHeaders.delete(lowerName);
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (value === clearSentinel || !seenHeaders.has(lowerName)) {
|
|
81
123
|
targetHeaders.delete(name);
|
|
82
124
|
seenHeaders.add(lowerName);
|
|
125
|
+
if (value === clearSentinel) continue;
|
|
83
126
|
}
|
|
84
127
|
if (value === null) {
|
|
85
128
|
targetHeaders.delete(name);
|
package/src/internal/parse.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { FinalRequestOptions } from './request-options';
|
|
|
4
4
|
import { Stream } from '../core/streaming';
|
|
5
5
|
import { type BaseAnthropic } from '../client';
|
|
6
6
|
import { formatRequestDetails, loggerFor } from './utils/log';
|
|
7
|
+
import { releaseRequestSignal } from './request-signal';
|
|
7
8
|
import type { AbstractPage } from '../core/pagination';
|
|
8
9
|
|
|
9
10
|
export type APIResponseProps = {
|
|
@@ -55,7 +56,15 @@ export async function defaultParseResponse<T>(
|
|
|
55
56
|
|
|
56
57
|
const text = await response.text();
|
|
57
58
|
return text as unknown as T;
|
|
58
|
-
})()
|
|
59
|
+
})().finally(() => {
|
|
60
|
+
// The body is settled (or parsing threw), so the caller-signal abort
|
|
61
|
+
// listener has nothing left to cancel. Streams release in their own
|
|
62
|
+
// teardown; a raw Response (`__binaryResponse`) keeps the listener so
|
|
63
|
+
// aborting an in-flight download still works.
|
|
64
|
+
if (!props.options.stream && !props.options.__binaryResponse) {
|
|
65
|
+
releaseRequestSignal(props.controller);
|
|
66
|
+
}
|
|
67
|
+
});
|
|
59
68
|
loggerFor(client).debug(
|
|
60
69
|
`[${requestLogID}] response parsed`,
|
|
61
70
|
formatRequestDetails({
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tracks the removal of the per-request abort listener that
|
|
3
|
+
* `fetchWithTimeout` attaches to a caller-provided signal, so the listener's
|
|
4
|
+
* lifetime matches the request instead of the signal.
|
|
5
|
+
*
|
|
6
|
+
* Without removal, a long-lived signal (e.g. one AbortController reused for
|
|
7
|
+
* a whole session) accumulates one `{ once: true }` listener plus its bound
|
|
8
|
+
* AbortController per HTTP attempt until the signal fires or is collected,
|
|
9
|
+
* and Node warns at the 11th listener. The listener must survive until the
|
|
10
|
+
* response body is settled - removing it when fetch resolves (headers) would
|
|
11
|
+
* break aborting an in-flight body read - so the code that finishes the body
|
|
12
|
+
* (response parsing, stream teardown, retry/error handling) calls
|
|
13
|
+
* `releaseRequestSignal` with the request's controller.
|
|
14
|
+
*/
|
|
15
|
+
const cleanups = new WeakMap<AbortController, () => void>();
|
|
16
|
+
|
|
17
|
+
// Backstop for requests that never reach an explicit release point: a caller
|
|
18
|
+
// that partially iterates a stream (or receives a raw binary Response) and
|
|
19
|
+
// drops the reference never completes, errors, or cancels it, so the only
|
|
20
|
+
// remaining lifecycle event is the response being collected. Finalization
|
|
21
|
+
// timing is GC-driven and not guaranteed, so this only bounds abandonment -
|
|
22
|
+
// the explicit release calls stay the primary cleanup. The held value must
|
|
23
|
+
// not reference the response, or it could never be collected. Typed
|
|
24
|
+
// structurally because the compiler lib is es2020.
|
|
25
|
+
type AbandonmentRegistry = {
|
|
26
|
+
register(target: object, heldValue: AbortController, token: object): void;
|
|
27
|
+
unregister(token: object): void;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const registry: AbandonmentRegistry | null =
|
|
31
|
+
typeof (globalThis as any).FinalizationRegistry === 'function' ?
|
|
32
|
+
new (globalThis as any).FinalizationRegistry((controller: AbortController) =>
|
|
33
|
+
releaseRequestSignal(controller),
|
|
34
|
+
)
|
|
35
|
+
: null;
|
|
36
|
+
|
|
37
|
+
// Module-scope factory so the cleanup closure captures exactly the signal and
|
|
38
|
+
// the listener - built at the `fetchWithTimeout` call site it would share that
|
|
39
|
+
// scope's context and retain the request body for as long as the cleanup is
|
|
40
|
+
// held (same reason `_makeAbort` exists in client.ts).
|
|
41
|
+
function makeCleanup(signal: AbortSignal, listener: () => void): () => void {
|
|
42
|
+
return () => signal.removeEventListener('abort', listener);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function registerRequestSignalCleanup(
|
|
46
|
+
controller: AbortController,
|
|
47
|
+
signal: AbortSignal,
|
|
48
|
+
listener: () => void,
|
|
49
|
+
): void {
|
|
50
|
+
cleanups.set(controller, makeCleanup(signal, listener));
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// The registered target is the response BODY, not the Response: a caller can
|
|
54
|
+
// keep a reader on the body and drop the Response wrapper (`.asResponse()`),
|
|
55
|
+
// and the listener must survive for as long as anything can still read - the
|
|
56
|
+
// body is what a live read keeps alive.
|
|
57
|
+
export function armAbandonmentBackstop(body: object, controller: AbortController): void {
|
|
58
|
+
if (cleanups.has(controller)) registry?.register(body, controller, controller);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function releaseRequestSignal(controller: AbortController): void {
|
|
62
|
+
const cleanup = cleanups.get(controller);
|
|
63
|
+
if (cleanup) {
|
|
64
|
+
cleanups.delete(controller);
|
|
65
|
+
registry?.unregister(controller);
|
|
66
|
+
cleanup();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single source of truth for the `x-stainless-helper` telemetry header — the
|
|
3
|
+
* key, the closed value vocabulary, and per-object helper tagging. The
|
|
4
|
+
* append-don't-clobber merge for the header itself lives in
|
|
5
|
+
* {@link import('../internal/headers').buildHeaders} via `APPEND_HEADERS`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Telemetry header naming the SDK helper(s) a request came from. Always this
|
|
10
|
+
* lowercase form; `buildHeaders` matches it case-insensitively for its append
|
|
11
|
+
* semantics, but a single canonical casing keeps every call site greppable.
|
|
12
|
+
*/
|
|
13
|
+
export const STAINLESS_HELPER_HEADER = 'x-stainless-helper';
|
|
14
|
+
|
|
15
|
+
/** Telemetry header naming the SDK method (e.g. `stream`) in use. */
|
|
16
|
+
export const STAINLESS_HELPER_METHOD_HEADER = 'x-stainless-helper-method';
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The closed set of helper telemetry tags, shared verbatim across SDKs. A
|
|
20
|
+
* typo at any call site is a type error rather than silently mistagged
|
|
21
|
+
* telemetry. Existing values keep their original spellings — telemetry
|
|
22
|
+
* consumers match on them, so renames lose history. New tags are hyphenated
|
|
23
|
+
* lowercase.
|
|
24
|
+
*/
|
|
25
|
+
export type StainlessHelperHeaderValue =
|
|
26
|
+
| 'BetaToolRunner'
|
|
27
|
+
| 'betaZodTool'
|
|
28
|
+
| 'compaction'
|
|
29
|
+
| 'environments-work-poller'
|
|
30
|
+
| 'environments-worker'
|
|
31
|
+
| 'fallback-refusal-middleware'
|
|
32
|
+
| 'mcpContent'
|
|
33
|
+
| 'mcpMessage'
|
|
34
|
+
| 'mcpResourceToContent'
|
|
35
|
+
| 'mcpResourceToFile'
|
|
36
|
+
| 'mcpTool'
|
|
37
|
+
| 'session-tool-runner';
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The `{ 'x-stainless-helper': value }` header dict, for passing into
|
|
41
|
+
* `buildHeaders` (which comma-appends `x-stainless-helper` across sources)
|
|
42
|
+
* or as `defaultHeaders`/per-request `headers`.
|
|
43
|
+
*/
|
|
44
|
+
export function helperHeader(value: StainlessHelperHeaderValue): { [STAINLESS_HELPER_HEADER]: string } {
|
|
45
|
+
return { [STAINLESS_HELPER_HEADER]: value };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Symbol used to mark objects created by SDK helpers for tracking.
|
|
50
|
+
* The value is the helper name (e.g., 'mcpTool', 'betaZodTool').
|
|
51
|
+
*/
|
|
52
|
+
export const SDK_HELPER_SYMBOL = Symbol('anthropic.sdk.stainlessHelper');
|
|
53
|
+
|
|
54
|
+
type StainlessHelperObject = { [SDK_HELPER_SYMBOL]: string };
|
|
55
|
+
|
|
56
|
+
export function wasCreatedByStainlessHelper(value: unknown): value is StainlessHelperObject {
|
|
57
|
+
return typeof value === 'object' && value !== null && SDK_HELPER_SYMBOL in value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Collects helper names from tools and messages arrays.
|
|
62
|
+
* Returns a deduplicated array of helper names found.
|
|
63
|
+
*/
|
|
64
|
+
export function collectStainlessHelpers(
|
|
65
|
+
tools: readonly unknown[] | undefined,
|
|
66
|
+
messages: readonly unknown[] | undefined,
|
|
67
|
+
): string[] {
|
|
68
|
+
const helpers = new Set<string>();
|
|
69
|
+
|
|
70
|
+
// Collect from tools
|
|
71
|
+
if (tools) {
|
|
72
|
+
for (const tool of tools) {
|
|
73
|
+
if (wasCreatedByStainlessHelper(tool)) {
|
|
74
|
+
helpers.add(tool[SDK_HELPER_SYMBOL]);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Collect from messages and their content blocks
|
|
80
|
+
if (messages) {
|
|
81
|
+
for (const message of messages) {
|
|
82
|
+
if (wasCreatedByStainlessHelper(message)) {
|
|
83
|
+
helpers.add(message[SDK_HELPER_SYMBOL]);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const content = (message as { content?: unknown }).content;
|
|
87
|
+
if (Array.isArray(content)) {
|
|
88
|
+
for (const block of content) {
|
|
89
|
+
if (wasCreatedByStainlessHelper(block)) {
|
|
90
|
+
helpers.add(block[SDK_HELPER_SYMBOL]);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(helpers);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Builds x-stainless-helper header value from tools and messages.
|
|
102
|
+
* Returns an empty object if no helpers are found.
|
|
103
|
+
*/
|
|
104
|
+
export function stainlessHelperHeader(
|
|
105
|
+
tools: readonly unknown[] | undefined,
|
|
106
|
+
messages: readonly unknown[] | undefined,
|
|
107
|
+
): { 'x-stainless-helper'?: string } {
|
|
108
|
+
const helpers = collectStainlessHelpers(tools, messages);
|
|
109
|
+
if (helpers.length === 0) return {};
|
|
110
|
+
return { [STAINLESS_HELPER_HEADER]: helpers.join(', ') };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Builds x-stainless-helper header value from a file object.
|
|
115
|
+
* Returns an empty object if the file is not marked with a helper.
|
|
116
|
+
*/
|
|
117
|
+
export function stainlessHelperHeaderFromFile(file: unknown): { 'x-stainless-helper'?: string } {
|
|
118
|
+
if (wasCreatedByStainlessHelper(file)) {
|
|
119
|
+
return { [STAINLESS_HELPER_HEADER]: file[SDK_HELPER_SYMBOL] };
|
|
120
|
+
}
|
|
121
|
+
return {};
|
|
122
|
+
}
|