openai 7.27.0 → 7.30.0
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 +23 -0
- package/client.d.mts +3 -0
- package/client.d.mts.map +1 -1
- package/client.d.ts +3 -0
- package/client.d.ts.map +1 -1
- package/client.js +3 -0
- package/client.js.map +1 -1
- package/client.mjs +3 -0
- package/client.mjs.map +1 -1
- package/helpers/standard-schema.d.mts +8 -3
- package/helpers/standard-schema.d.mts.map +1 -1
- package/helpers/standard-schema.d.ts +8 -3
- package/helpers/standard-schema.d.ts.map +1 -1
- package/helpers/standard-schema.js +1 -0
- package/helpers/standard-schema.js.map +1 -1
- package/helpers/standard-schema.mjs +1 -0
- package/helpers/standard-schema.mjs.map +1 -1
- package/helpers/zod.d.mts +2 -0
- package/helpers/zod.d.mts.map +1 -1
- package/helpers/zod.d.ts +2 -0
- package/helpers/zod.d.ts.map +1 -1
- package/helpers/zod.js +1 -0
- package/helpers/zod.js.map +1 -1
- package/helpers/zod.mjs +1 -0
- package/helpers/zod.mjs.map +1 -1
- package/lib/ChatCompletionStream.d.mts +2 -1
- package/lib/ChatCompletionStream.d.mts.map +1 -1
- package/lib/ChatCompletionStream.d.ts +2 -1
- package/lib/ChatCompletionStream.d.ts.map +1 -1
- package/lib/ChatCompletionStream.js +9 -2
- package/lib/ChatCompletionStream.js.map +1 -1
- package/lib/ChatCompletionStream.mjs +9 -2
- package/lib/ChatCompletionStream.mjs.map +1 -1
- package/lib/ResponsesParser.d.mts.map +1 -1
- package/lib/ResponsesParser.d.ts.map +1 -1
- package/lib/ResponsesParser.js +5 -0
- package/lib/ResponsesParser.js.map +1 -1
- package/lib/ResponsesParser.mjs +5 -0
- package/lib/ResponsesParser.mjs.map +1 -1
- package/lib/agents/agent-session-stream.d.mts +7 -0
- package/lib/agents/agent-session-stream.d.mts.map +1 -1
- package/lib/agents/agent-session-stream.d.ts +7 -0
- package/lib/agents/agent-session-stream.d.ts.map +1 -1
- package/lib/agents/agent-session-stream.js +8 -110
- package/lib/agents/agent-session-stream.js.map +1 -1
- package/lib/agents/agent-session-stream.mjs +9 -111
- package/lib/agents/agent-session-stream.mjs.map +1 -1
- package/lib/beta/agents/agent-session-create-stream.d.mts +41 -1
- package/lib/beta/agents/agent-session-create-stream.d.mts.map +1 -1
- package/lib/beta/agents/agent-session-create-stream.d.ts +41 -1
- package/lib/beta/agents/agent-session-create-stream.d.ts.map +1 -1
- package/lib/beta/agents/agent-session-create-stream.js +70 -2
- package/lib/beta/agents/agent-session-create-stream.js.map +1 -1
- package/lib/beta/agents/agent-session-create-stream.mjs +69 -2
- package/lib/beta/agents/agent-session-create-stream.mjs.map +1 -1
- package/lib/beta/agents/function-tool.d.mts.map +1 -1
- package/lib/beta/agents/function-tool.d.ts.map +1 -1
- package/lib/beta/agents/function-tool.js +22 -15
- package/lib/beta/agents/function-tool.js.map +1 -1
- package/lib/beta/agents/function-tool.mjs +22 -15
- package/lib/beta/agents/function-tool.mjs.map +1 -1
- package/lib/beta/agents/parse-result.d.mts +4 -2
- package/lib/beta/agents/parse-result.d.mts.map +1 -1
- package/lib/beta/agents/parse-result.d.ts +4 -2
- package/lib/beta/agents/parse-result.d.ts.map +1 -1
- package/lib/beta/agents/tool-dispatcher.d.mts +13 -0
- package/lib/beta/agents/tool-dispatcher.d.mts.map +1 -0
- package/lib/beta/agents/tool-dispatcher.d.ts +13 -0
- package/lib/beta/agents/tool-dispatcher.d.ts.map +1 -0
- package/lib/beta/agents/tool-dispatcher.js +185 -0
- package/lib/beta/agents/tool-dispatcher.js.map +1 -0
- package/lib/beta/agents/tool-dispatcher.mjs +181 -0
- package/lib/beta/agents/tool-dispatcher.mjs.map +1 -0
- package/lib/beta/agents/tool-error.d.mts +13 -0
- package/lib/beta/agents/tool-error.d.mts.map +1 -0
- package/lib/beta/agents/tool-error.d.ts +13 -0
- package/lib/beta/agents/tool-error.d.ts.map +1 -0
- package/lib/beta/agents/tool-error.js +3 -0
- package/lib/beta/agents/tool-error.js.map +1 -0
- package/lib/beta/agents/tool-error.mjs +2 -0
- package/lib/beta/agents/tool-error.mjs.map +1 -0
- package/lib/beta/agents/tool-stages.d.mts +12 -0
- package/lib/beta/agents/tool-stages.d.mts.map +1 -0
- package/lib/beta/agents/tool-stages.d.ts +12 -0
- package/lib/beta/agents/tool-stages.d.ts.map +1 -0
- package/lib/beta/agents/tool-stages.js +6 -0
- package/lib/beta/agents/tool-stages.js.map +1 -0
- package/lib/beta/agents/tool-stages.mjs +3 -0
- package/lib/beta/agents/tool-stages.mjs.map +1 -0
- package/lib/chat-completions/streaming.d.mts +11 -0
- package/lib/chat-completions/streaming.d.mts.map +1 -0
- package/lib/chat-completions/streaming.d.ts +11 -0
- package/lib/chat-completions/streaming.d.ts.map +1 -0
- package/lib/chat-completions/streaming.js +27 -0
- package/lib/chat-completions/streaming.js.map +1 -0
- package/lib/chat-completions/streaming.mjs +23 -0
- package/lib/chat-completions/streaming.mjs.map +1 -0
- package/package.json +1 -1
- package/resources/admin/organization/usage.d.mts +381 -20
- package/resources/admin/organization/usage.d.mts.map +1 -1
- package/resources/admin/organization/usage.d.ts +381 -20
- package/resources/admin/organization/usage.d.ts.map +1 -1
- package/resources/audio/audio.d.mts +4 -0
- package/resources/audio/audio.d.mts.map +1 -1
- package/resources/audio/audio.d.ts +4 -0
- package/resources/audio/audio.d.ts.map +1 -1
- package/resources/audio/audio.js +4 -0
- package/resources/audio/audio.js.map +1 -1
- package/resources/audio/audio.mjs +4 -0
- package/resources/audio/audio.mjs.map +1 -1
- package/resources/audio/index.d.mts +1 -0
- package/resources/audio/index.d.mts.map +1 -1
- package/resources/audio/index.d.ts +1 -0
- package/resources/audio/index.d.ts.map +1 -1
- package/resources/audio/index.js +3 -1
- package/resources/audio/index.js.map +1 -1
- package/resources/audio/index.mjs +1 -0
- package/resources/audio/index.mjs.map +1 -1
- package/resources/audio/speech.d.mts +2 -0
- package/resources/audio/speech.d.mts.map +1 -1
- package/resources/audio/speech.d.ts +2 -0
- package/resources/audio/speech.d.ts.map +1 -1
- package/resources/audio/voices.d.mts +119 -0
- package/resources/audio/voices.d.mts.map +1 -0
- package/resources/audio/voices.d.ts +119 -0
- package/resources/audio/voices.d.ts.map +1 -0
- package/resources/audio/voices.js +41 -0
- package/resources/audio/voices.js.map +1 -0
- package/resources/audio/voices.mjs +37 -0
- package/resources/audio/voices.mjs.map +1 -0
- package/resources/beta/agents/agents.d.mts +14 -10
- package/resources/beta/agents/agents.d.mts.map +1 -1
- package/resources/beta/agents/agents.d.ts +14 -10
- package/resources/beta/agents/agents.d.ts.map +1 -1
- package/resources/beta/agents/agents.js.map +1 -1
- package/resources/beta/agents/agents.mjs.map +1 -1
- package/resources/beta/agents/environments/templates.d.mts +6 -8
- package/resources/beta/agents/environments/templates.d.mts.map +1 -1
- package/resources/beta/agents/environments/templates.d.ts +6 -8
- package/resources/beta/agents/environments/templates.d.ts.map +1 -1
- package/resources/beta/agents/sessions/index.d.mts +1 -1
- package/resources/beta/agents/sessions/index.d.mts.map +1 -1
- package/resources/beta/agents/sessions/index.d.ts +1 -1
- package/resources/beta/agents/sessions/index.d.ts.map +1 -1
- package/resources/beta/agents/sessions/index.js +2 -2
- package/resources/beta/agents/sessions/index.js.map +1 -1
- package/resources/beta/agents/sessions/index.mjs +1 -1
- package/resources/beta/agents/sessions/index.mjs.map +1 -1
- package/resources/beta/agents/sessions/sessions.d.mts +8 -7
- package/resources/beta/agents/sessions/sessions.d.mts.map +1 -1
- package/resources/beta/agents/sessions/sessions.d.ts +8 -7
- package/resources/beta/agents/sessions/sessions.d.ts.map +1 -1
- package/resources/beta/agents/sessions/sessions.js +7 -4
- package/resources/beta/agents/sessions/sessions.js.map +1 -1
- package/resources/beta/agents/sessions/sessions.mjs +8 -5
- package/resources/beta/agents/sessions/sessions.mjs.map +1 -1
- package/resources/beta/agents/sessions/subagents/turns/turns.d.mts +2 -2
- package/resources/beta/agents/sessions/subagents/turns/turns.d.mts.map +1 -1
- package/resources/beta/agents/sessions/subagents/turns/turns.d.ts +2 -2
- package/resources/beta/agents/sessions/subagents/turns/turns.d.ts.map +1 -1
- package/resources/beta/agents/sessions/turns/index.d.mts +3 -0
- package/resources/beta/agents/sessions/turns/index.d.mts.map +1 -0
- package/resources/beta/agents/sessions/turns/index.d.ts +3 -0
- package/resources/beta/agents/sessions/turns/index.d.ts.map +1 -0
- package/resources/beta/agents/sessions/turns/index.js +9 -0
- package/resources/beta/agents/sessions/turns/index.js.map +1 -0
- package/resources/beta/agents/sessions/turns/index.mjs +4 -0
- package/resources/beta/agents/sessions/turns/index.mjs.map +1 -0
- package/resources/beta/agents/sessions/turns/items.d.mts +43 -0
- package/resources/beta/agents/sessions/turns/items.d.mts.map +1 -0
- package/resources/beta/agents/sessions/turns/items.d.ts +43 -0
- package/resources/beta/agents/sessions/turns/items.d.ts.map +1 -0
- package/resources/beta/agents/sessions/turns/items.js +40 -0
- package/resources/beta/agents/sessions/turns/items.js.map +1 -0
- package/resources/beta/agents/sessions/turns/items.mjs +36 -0
- package/resources/beta/agents/sessions/turns/items.mjs.map +1 -0
- package/resources/beta/agents/sessions/turns/turns.d.mts +128 -0
- package/resources/beta/agents/sessions/turns/turns.d.mts.map +1 -0
- package/resources/beta/agents/sessions/turns/turns.d.ts +128 -0
- package/resources/beta/agents/sessions/turns/turns.d.ts.map +1 -0
- package/resources/beta/agents/sessions/turns/turns.js +111 -0
- package/resources/beta/agents/sessions/turns/turns.js.map +1 -0
- package/resources/beta/agents/sessions/turns/turns.mjs +106 -0
- package/resources/beta/agents/sessions/turns/turns.mjs.map +1 -0
- package/resources/beta/agents/sessions/turns.d.mts +1 -123
- package/resources/beta/agents/sessions/turns.d.mts.map +1 -1
- package/resources/beta/agents/sessions/turns.d.ts +1 -123
- package/resources/beta/agents/sessions/turns.d.ts.map +1 -1
- package/resources/beta/agents/sessions/turns.js +2 -99
- package/resources/beta/agents/sessions/turns.js.map +1 -1
- package/resources/beta/agents/sessions/turns.mjs +1 -97
- package/resources/beta/agents/sessions/turns.mjs.map +1 -1
- package/resources/beta/beta.d.mts +2 -2
- package/resources/beta/beta.d.mts.map +1 -1
- package/resources/beta/beta.d.ts +2 -2
- package/resources/beta/beta.d.ts.map +1 -1
- package/resources/beta/beta.js.map +1 -1
- package/resources/beta/beta.mjs.map +1 -1
- package/resources/beta/index.d.mts +1 -1
- package/resources/beta/index.d.mts.map +1 -1
- package/resources/beta/index.d.ts +1 -1
- package/resources/beta/index.d.ts.map +1 -1
- package/resources/beta/index.js.map +1 -1
- package/resources/beta/index.mjs.map +1 -1
- package/resources/beta/responses/index.d.mts +1 -1
- package/resources/beta/responses/index.d.mts.map +1 -1
- package/resources/beta/responses/index.d.ts +1 -1
- package/resources/beta/responses/index.d.ts.map +1 -1
- package/resources/beta/responses/index.js.map +1 -1
- package/resources/beta/responses/index.mjs.map +1 -1
- package/resources/beta/responses/responses.d.mts +379 -9
- package/resources/beta/responses/responses.d.mts.map +1 -1
- package/resources/beta/responses/responses.d.ts +379 -9
- package/resources/beta/responses/responses.d.ts.map +1 -1
- package/resources/beta/responses/responses.js.map +1 -1
- package/resources/beta/responses/responses.mjs.map +1 -1
- package/resources/chat/completions/completions.d.mts +44 -9
- package/resources/chat/completions/completions.d.mts.map +1 -1
- package/resources/chat/completions/completions.d.ts +44 -9
- package/resources/chat/completions/completions.d.ts.map +1 -1
- package/resources/chat/completions/completions.js +5 -2
- package/resources/chat/completions/completions.js.map +1 -1
- package/resources/chat/completions/completions.mjs +5 -2
- package/resources/chat/completions/completions.mjs.map +1 -1
- package/resources/completions.d.mts +1 -1
- package/resources/completions.d.mts.map +1 -1
- package/resources/completions.d.ts +1 -1
- package/resources/completions.d.ts.map +1 -1
- package/resources/decisions.d.mts +196 -0
- package/resources/decisions.d.mts.map +1 -0
- package/resources/decisions.d.ts +196 -0
- package/resources/decisions.d.ts.map +1 -0
- package/resources/decisions.js +32 -0
- package/resources/decisions.js.map +1 -0
- package/resources/decisions.mjs +28 -0
- package/resources/decisions.mjs.map +1 -0
- package/resources/fine-tuning/jobs/jobs.d.mts +7 -2
- package/resources/fine-tuning/jobs/jobs.d.mts.map +1 -1
- package/resources/fine-tuning/jobs/jobs.d.ts +7 -2
- package/resources/fine-tuning/jobs/jobs.d.ts.map +1 -1
- package/resources/fine-tuning/jobs/jobs.js.map +1 -1
- package/resources/fine-tuning/jobs/jobs.mjs.map +1 -1
- package/resources/graders/grader-models.d.mts +1 -1
- package/resources/graders/grader-models.d.ts +1 -1
- package/resources/images.d.mts +61 -75
- package/resources/images.d.mts.map +1 -1
- package/resources/images.d.ts +61 -75
- package/resources/images.d.ts.map +1 -1
- package/resources/images.js +4 -7
- package/resources/images.js.map +1 -1
- package/resources/images.mjs +4 -7
- package/resources/images.mjs.map +1 -1
- package/resources/index.d.mts +1 -0
- package/resources/index.d.mts.map +1 -1
- package/resources/index.d.ts +1 -0
- package/resources/index.d.ts.map +1 -1
- package/resources/index.js +3 -1
- package/resources/index.js.map +1 -1
- package/resources/index.mjs +1 -0
- package/resources/index.mjs.map +1 -1
- package/resources/realtime/realtime.d.mts +16 -4
- package/resources/realtime/realtime.d.mts.map +1 -1
- package/resources/realtime/realtime.d.ts +16 -4
- package/resources/realtime/realtime.d.ts.map +1 -1
- package/resources/realtime/realtime.js.map +1 -1
- package/resources/realtime/realtime.mjs.map +1 -1
- package/resources/responses/responses.d.mts +381 -9
- package/resources/responses/responses.d.mts.map +1 -1
- package/resources/responses/responses.d.ts +381 -9
- package/resources/responses/responses.d.ts.map +1 -1
- package/resources/responses/responses.js.map +1 -1
- package/resources/responses/responses.mjs.map +1 -1
- package/resources/shared.d.mts +69 -0
- package/resources/shared.d.mts.map +1 -1
- package/resources/shared.d.ts +69 -0
- package/resources/shared.d.ts.map +1 -1
- package/resources/videos.d.mts +5 -0
- package/resources/videos.d.mts.map +1 -1
- package/resources/videos.d.ts +5 -0
- package/resources/videos.d.ts.map +1 -1
- package/resources/webhooks/webhooks.d.mts +211 -6
- package/resources/webhooks/webhooks.d.mts.map +1 -1
- package/resources/webhooks/webhooks.d.ts +211 -6
- package/resources/webhooks/webhooks.d.ts.map +1 -1
- package/resources/webhooks/webhooks.js.map +1 -1
- package/resources/webhooks/webhooks.mjs.map +1 -1
- package/src/client.ts +21 -0
- package/src/helpers/standard-schema.ts +11 -4
- package/src/helpers/zod.ts +4 -0
- package/src/lib/ChatCompletionStream.ts +10 -3
- package/src/lib/ResponsesParser.ts +8 -0
- package/src/lib/agents/agent-session-stream.ts +20 -124
- package/src/lib/beta/agents/agent-session-create-stream.ts +87 -1
- package/src/lib/beta/agents/function-tool.ts +23 -15
- package/src/lib/beta/agents/tool-dispatcher.ts +226 -0
- package/src/lib/beta/agents/tool-error.ts +12 -0
- package/src/lib/beta/agents/tool-stages.ts +14 -0
- package/src/lib/chat-completions/streaming.ts +35 -0
- package/src/resources/admin/organization/usage.ts +438 -20
- package/src/resources/audio/audio.ts +6 -0
- package/src/resources/audio/index.ts +1 -0
- package/src/resources/audio/speech.ts +2 -0
- package/src/resources/audio/voices.ts +152 -0
- package/src/resources/beta/agents/agents.ts +16 -9
- package/src/resources/beta/agents/environments/templates.ts +6 -8
- package/src/resources/beta/agents/sessions/index.ts +7 -1
- package/src/resources/beta/agents/sessions/sessions.ts +18 -8
- package/src/resources/beta/agents/sessions/subagents/turns/turns.ts +2 -2
- package/src/resources/beta/agents/sessions/turns/index.ts +4 -0
- package/src/resources/beta/agents/sessions/turns/items.ts +79 -0
- package/src/resources/beta/agents/sessions/turns/turns.ts +383 -0
- package/src/resources/beta/agents/sessions/turns.ts +1 -373
- package/src/resources/beta/beta.ts +2 -0
- package/src/resources/beta/index.ts +1 -0
- package/src/resources/beta/responses/index.ts +1 -0
- package/src/resources/beta/responses/responses.ts +471 -8
- package/src/resources/chat/completions/completions.ts +67 -19
- package/src/resources/completions.ts +1 -1
- package/src/resources/decisions.ts +290 -0
- package/src/resources/fine-tuning/jobs/jobs.ts +7 -2
- package/src/resources/graders/grader-models.ts +1 -1
- package/src/resources/images.ts +62 -76
- package/src/resources/index.ts +9 -0
- package/src/resources/realtime/realtime.ts +16 -4
- package/src/resources/responses/api.md +1 -0
- package/src/resources/responses/responses.ts +473 -8
- package/src/resources/shared.ts +70 -0
- package/src/resources/videos.ts +6 -0
- package/src/resources/webhooks/api.md +5 -0
- package/src/resources/webhooks/webhooks.ts +273 -1
- package/src/version.ts +1 -1
- package/version.d.mts +1 -1
- package/version.d.ts +1 -1
- package/version.js +1 -1
- package/version.mjs +1 -1
|
@@ -276,6 +276,14 @@ function getInputToolByName(
|
|
|
276
276
|
);
|
|
277
277
|
}
|
|
278
278
|
}
|
|
279
|
+
// Hosted discovery exposes a deferred top-level function under its own name.
|
|
280
|
+
// A declared namespace above owns that identity, even if it has no matching function.
|
|
281
|
+
if (namespace === name) {
|
|
282
|
+
return input_tools.find(
|
|
283
|
+
(tool): tool is FunctionTool =>
|
|
284
|
+
tool.type === 'function' && tool.name === name && tool.defer_loading === true,
|
|
285
|
+
);
|
|
286
|
+
}
|
|
279
287
|
return undefined;
|
|
280
288
|
}
|
|
281
289
|
|
|
@@ -1,16 +1,15 @@
|
|
|
1
1
|
import type { AgentOutputFormat, AgentResult } from '../beta/agents/output-format-types';
|
|
2
2
|
import { TurnState } from './turn-state';
|
|
3
|
+
import { AgentToolDispatcher } from '../beta/agents/tool-dispatcher';
|
|
3
4
|
import { agentFormatParser, parseAgentResultPromise } from '../beta/agents/parse-result';
|
|
4
5
|
import { ResultCollection } from '../beta/agents/result-collection';
|
|
5
|
-
import { APIUserAbortError,
|
|
6
|
+
import { APIUserAbortError, OpenAIError } from '../../core/error';
|
|
6
7
|
import type { Stream } from '../../core/streaming';
|
|
7
8
|
import { buildHeaders } from '../../internal/headers';
|
|
8
9
|
import type { RequestOptions } from '../../internal/request-options';
|
|
9
10
|
import { uuid4 } from '../../internal/utils/uuid';
|
|
10
|
-
import {
|
|
11
|
-
import { isInputContent } from '../beta/agents/tool-output';
|
|
11
|
+
import type { AgentToolError } from '../beta/agents/tool-error';
|
|
12
12
|
import type {
|
|
13
|
-
AgentFunctionCallItem,
|
|
14
13
|
AgentFunctionCallOutputParam,
|
|
15
14
|
AgentSessionEvent,
|
|
16
15
|
AgentSessionInputMessageParam,
|
|
@@ -33,39 +32,16 @@ export type AgentSessionStreamParams<T = never> = {
|
|
|
33
32
|
input: string | AgentSessionInputMessageParam[];
|
|
34
33
|
/** Registered functions run after their call event is yielded; unknown functions remain manual. */
|
|
35
34
|
toolHandlers?: Record<string, AgentToolHandler>;
|
|
35
|
+
/**
|
|
36
|
+
* Beta: use alongside `toolHandlers` to log or monitor local argument validation,
|
|
37
|
+
* handler execution, and output serialization failures. Does not observe API or
|
|
38
|
+
* transport errors. Observer errors are ignored; model-visible errors remain sanitized.
|
|
39
|
+
*/
|
|
40
|
+
onToolError?: (failure: AgentToolError) => void | PromiseLike<void>;
|
|
36
41
|
/** Key for the input submission only; request headers take precedence, case-insensitively. */
|
|
37
42
|
idempotencyKey?: string;
|
|
38
43
|
} & ([T] extends [never] ? unknown : { outputFormat: AgentOutputFormat<T> });
|
|
39
44
|
|
|
40
|
-
type ToolResult = AgentSessionInputParam.SessionInputParamAgentSessionInputToolResult;
|
|
41
|
-
|
|
42
|
-
function normalizedOutput(value: unknown): AgentFunctionCallOutputParam | null {
|
|
43
|
-
if (value === null) {
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
if (typeof value === 'string' || (Array.isArray(value) && value.every(isInputContent))) {
|
|
47
|
-
return value;
|
|
48
|
-
}
|
|
49
|
-
throw new OpenAIError('Tool output must be text, content, a JSON object, or null');
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// oxlint-disable-next-line anti-slop/no-object-parameters -- The public AgentToolOutput contract accepts arbitrary JSON-serializable object results.
|
|
53
|
-
function toolResult(call: AgentFunctionCallItem, value: AgentToolOutput): ToolResult {
|
|
54
|
-
const output = isObj(value) ? JSON.stringify(value) : value;
|
|
55
|
-
// Detect unserializable callback results inside the redacted failure boundary.
|
|
56
|
-
const serialized = JSON.stringify(output);
|
|
57
|
-
if (serialized === undefined) {
|
|
58
|
-
throw new OpenAIError('Tool output must be JSON serializable');
|
|
59
|
-
}
|
|
60
|
-
return {
|
|
61
|
-
type: 'agent.session.input.tool_result',
|
|
62
|
-
turn_id: call.turn_id,
|
|
63
|
-
call_id: call.call_id,
|
|
64
|
-
success: true,
|
|
65
|
-
output: normalizedOutput(typeof output === 'string' ? output : JSON.parse(serialized)),
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
45
|
async function cancelBody(response: Response | undefined): Promise<void> {
|
|
70
46
|
try {
|
|
71
47
|
await response?.body?.cancel();
|
|
@@ -100,7 +76,7 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
100
76
|
#sessions: Sessions;
|
|
101
77
|
#sessionID: string;
|
|
102
78
|
#input: AgentSessionInputParam.SessionInputParamAgentSessionInputMessage;
|
|
103
|
-
#
|
|
79
|
+
#dispatcher: AgentToolDispatcher;
|
|
104
80
|
#inputKey: string | undefined;
|
|
105
81
|
#options: RequestOptions;
|
|
106
82
|
|
|
@@ -125,7 +101,6 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
125
101
|
this.#sessions = sessions;
|
|
126
102
|
this.#sessionID = sessionID;
|
|
127
103
|
this.#input = { type: 'agent.session.input.message', input };
|
|
128
|
-
this.#handlers = new Map(Object.entries(params.toolHandlers ?? {}));
|
|
129
104
|
const headers = buildHeaders([options?.headers]);
|
|
130
105
|
this.#inputKey = headers.nulls.has('idempotency-key')
|
|
131
106
|
? undefined
|
|
@@ -137,9 +112,16 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
137
112
|
headers.nulls.delete('idempotency-key');
|
|
138
113
|
const { idempotencyKey: _key, ...rest } = options ?? {};
|
|
139
114
|
this.#options = { ...rest, headers };
|
|
115
|
+
this.#dispatcher = new AgentToolDispatcher(
|
|
116
|
+
sessions,
|
|
117
|
+
params.toolHandlers ?? {},
|
|
118
|
+
this.controller,
|
|
119
|
+
this.#options,
|
|
120
|
+
params.onToolError,
|
|
121
|
+
);
|
|
140
122
|
this.#collection = new ResultCollection(
|
|
141
123
|
() => this.#iterate(),
|
|
142
|
-
(name) => this.#
|
|
124
|
+
(name) => this.#dispatcher.canHandle(name),
|
|
143
125
|
sessionID,
|
|
144
126
|
);
|
|
145
127
|
}
|
|
@@ -216,10 +198,7 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
216
198
|
continue;
|
|
217
199
|
}
|
|
218
200
|
const terminal = state.terminal(event);
|
|
219
|
-
const
|
|
220
|
-
const handler = pendingCall && this.#handlers.get(pendingCall.name);
|
|
221
|
-
// Freeze dispatch identity and arguments before exposing the original event.
|
|
222
|
-
const call = pendingCall && handler ? structuredClone(pendingCall) : undefined;
|
|
201
|
+
const dispatch = this.#dispatcher.prepare(state.call(event), this.#sessionID);
|
|
223
202
|
if (terminal) {
|
|
224
203
|
this.#stream.controller.abort();
|
|
225
204
|
}
|
|
@@ -228,12 +207,7 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
228
207
|
return;
|
|
229
208
|
}
|
|
230
209
|
this.#checkAbort();
|
|
231
|
-
|
|
232
|
-
continue;
|
|
233
|
-
}
|
|
234
|
-
const result = await this.#result(call, handler);
|
|
235
|
-
this.#checkAbort();
|
|
236
|
-
await this.#submit(result, options);
|
|
210
|
+
await dispatch?.();
|
|
237
211
|
}
|
|
238
212
|
this.#checkAbort();
|
|
239
213
|
throw new OpenAIError('Session event stream ended before the turn reached idle or failed');
|
|
@@ -244,26 +218,6 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
244
218
|
}
|
|
245
219
|
}
|
|
246
220
|
|
|
247
|
-
async #result(call: AgentFunctionCallItem, handler: AgentToolHandler): Promise<ToolResult> {
|
|
248
|
-
try {
|
|
249
|
-
const args: unknown = typeof call.arguments === 'string' ? JSON.parse(call.arguments) : call.arguments;
|
|
250
|
-
if (!isObj(args)) {
|
|
251
|
-
throw new OpenAIError('Function arguments must be a JSON object');
|
|
252
|
-
}
|
|
253
|
-
// SAFETY: Arguments were parsed as JSON and checked to be a non-null non-array object before invoking the handler.
|
|
254
|
-
return toolResult(call, await this.#wait(() => handler(args as Record<string, unknown>)));
|
|
255
|
-
} catch {
|
|
256
|
-
this.#checkAbort();
|
|
257
|
-
return {
|
|
258
|
-
type: 'agent.session.input.tool_result',
|
|
259
|
-
turn_id: call.turn_id,
|
|
260
|
-
call_id: call.call_id,
|
|
261
|
-
success: false,
|
|
262
|
-
error: 'Tool handler failed.',
|
|
263
|
-
};
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
|
|
267
221
|
#checkAbort(): void {
|
|
268
222
|
if (this.controller.signal.aborted) {
|
|
269
223
|
throw this.#abortError();
|
|
@@ -279,62 +233,4 @@ export class AgentSessionStream<T = never> implements AsyncIterable<AgentSession
|
|
|
279
233
|
});
|
|
280
234
|
return error;
|
|
281
235
|
}
|
|
282
|
-
|
|
283
|
-
async #wait<Value>(action: () => Value | PromiseLike<Value>): Promise<Value> {
|
|
284
|
-
let onAbort: (() => void) | undefined;
|
|
285
|
-
// oxlint-disable-next-line promise/avoid-new -- Bridge the caller's AbortSignal while a handler or registration delay is pending.
|
|
286
|
-
const aborted = new Promise<never>((_resolve, reject) => {
|
|
287
|
-
onAbort = () => reject(this.#abortError());
|
|
288
|
-
this.controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
289
|
-
});
|
|
290
|
-
try {
|
|
291
|
-
this.#checkAbort();
|
|
292
|
-
// Capture synchronous throws before racing cancellation, so both promises
|
|
293
|
-
// always have rejection handlers even if the callback aborts and throws.
|
|
294
|
-
const invoke = async () => await action();
|
|
295
|
-
return await Promise.race([invoke(), aborted]);
|
|
296
|
-
} finally {
|
|
297
|
-
if (onAbort) {
|
|
298
|
-
this.controller.signal.removeEventListener('abort', onAbort);
|
|
299
|
-
}
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async #submit(result: ToolResult, options: RequestOptions, key = uuid4(), attempt = 0): Promise<void> {
|
|
304
|
-
this.#checkAbort();
|
|
305
|
-
try {
|
|
306
|
-
await this.#sessions.events.create(
|
|
307
|
-
this.#sessionID,
|
|
308
|
-
{ events: [result], 'Idempotency-Key': key },
|
|
309
|
-
options,
|
|
310
|
-
);
|
|
311
|
-
} catch (error) {
|
|
312
|
-
const delay = [100, 300, 600][attempt];
|
|
313
|
-
if (
|
|
314
|
-
delay === undefined ||
|
|
315
|
-
!(error instanceof BadRequestError) ||
|
|
316
|
-
error.code !== 'invalid_request_error' ||
|
|
317
|
-
!error.error ||
|
|
318
|
-
!('message' in error.error) ||
|
|
319
|
-
error.error.message !== `Unknown pending tool call: ${result.call_id}`
|
|
320
|
-
) {
|
|
321
|
-
throw error;
|
|
322
|
-
}
|
|
323
|
-
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
324
|
-
try {
|
|
325
|
-
await this.#wait(
|
|
326
|
-
() =>
|
|
327
|
-
// oxlint-disable-next-line promise/avoid-new -- Own the registration timer so cancellation clears it promptly.
|
|
328
|
-
new Promise<void>((resolve) => {
|
|
329
|
-
timer = setTimeout(resolve, delay);
|
|
330
|
-
}),
|
|
331
|
-
);
|
|
332
|
-
} finally {
|
|
333
|
-
if (timer !== undefined) {
|
|
334
|
-
clearTimeout(timer);
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
await this.#submit(result, options, key, attempt + 1);
|
|
338
|
-
}
|
|
339
|
-
}
|
|
340
236
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
import { APIUserAbortError, OpenAIError } from '../../../core/error';
|
|
2
|
+
import { buildHeaders } from '../../../internal/headers';
|
|
3
|
+
import type { RequestOptions } from '../../../internal/request-options';
|
|
4
|
+
import type { Sessions, SessionCreateParams } from '../../../resources/beta/agents/sessions/sessions';
|
|
5
|
+
import type { AgentToolHandler } from '../../agents/agent-session-stream';
|
|
6
|
+
import { TurnState } from '../../agents/turn-state';
|
|
7
|
+
import { AgentToolDispatcher } from './tool-dispatcher';
|
|
1
8
|
import type { AgentOutputFormat, AgentResult } from './output-format-types';
|
|
2
9
|
import type { Stream } from '../../../core/streaming';
|
|
3
10
|
import type { AgentSessionEvent } from '../../../resources/beta/agents/agents';
|
|
@@ -12,16 +19,57 @@ export type AgentSessionCreateStream<T = never> = Stream<AgentSessionEvent> & {
|
|
|
12
19
|
withResultCollection: () => AgentSessionCreateStream<T>;
|
|
13
20
|
};
|
|
14
21
|
|
|
22
|
+
async function* dispatchCreationTools(
|
|
23
|
+
source: () => AsyncIterator<AgentSessionEvent>,
|
|
24
|
+
dispatcher: AgentToolDispatcher,
|
|
25
|
+
signal: AbortSignal,
|
|
26
|
+
): AsyncGenerator<AgentSessionEvent> {
|
|
27
|
+
const state = new TurnState();
|
|
28
|
+
let sessionID: string | undefined;
|
|
29
|
+
for await (const event of { [Symbol.asyncIterator]: source }) {
|
|
30
|
+
if (event.type === 'agent.session.created') {
|
|
31
|
+
sessionID ??= event.session.id;
|
|
32
|
+
}
|
|
33
|
+
const dispatch = state.accept(event) ? dispatcher.prepare(state.call(event), sessionID) : undefined;
|
|
34
|
+
yield event;
|
|
35
|
+
if (signal.aborted) {
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
await dispatch?.();
|
|
40
|
+
} catch (error) {
|
|
41
|
+
if (signal.aborted && error instanceof APIUserAbortError) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
15
49
|
/** Add beta result collection without replacing custom stream instances.
|
|
16
50
|
* @internal
|
|
17
51
|
*/
|
|
18
52
|
export function withAgentTurnResult<T = never>(
|
|
19
53
|
stream: Stream<AgentSessionEvent>,
|
|
20
54
|
format?: AgentOutputFormat<T>,
|
|
55
|
+
tools?: {
|
|
56
|
+
sessions: Sessions;
|
|
57
|
+
handlers: Record<string, AgentToolHandler>;
|
|
58
|
+
options?: RequestOptions | undefined;
|
|
59
|
+
},
|
|
21
60
|
): AgentSessionCreateStream<T> {
|
|
22
61
|
let collection: ResultCollection;
|
|
23
62
|
stream.__betaTransformIterator((source) => {
|
|
24
|
-
|
|
63
|
+
// A tool result has its own POST endpoint, independent of creation overrides.
|
|
64
|
+
const { path: _path, method: _method, stream: _stream, ...options } = tools?.options ?? {};
|
|
65
|
+
const dispatcher =
|
|
66
|
+
tools && new AgentToolDispatcher(tools.sessions, tools.handlers, stream.controller, options);
|
|
67
|
+
collection = new ResultCollection(
|
|
68
|
+
dispatcher ? () => dispatchCreationTools(source, dispatcher, stream.controller.signal) : source,
|
|
69
|
+
dispatcher ? (name) => dispatcher.canHandle(name) : undefined,
|
|
70
|
+
undefined,
|
|
71
|
+
stream.controller.signal,
|
|
72
|
+
);
|
|
25
73
|
return () => collection.iterate();
|
|
26
74
|
});
|
|
27
75
|
let parsed: Promise<AgentResult<T>> | undefined;
|
|
@@ -34,3 +82,41 @@ export function withAgentTurnResult<T = never>(
|
|
|
34
82
|
});
|
|
35
83
|
return result;
|
|
36
84
|
}
|
|
85
|
+
|
|
86
|
+
/** Remove local callbacks without changing ordinary creation requests. @internal */
|
|
87
|
+
export function captureCreationTools(body: SessionCreateParams, options?: RequestOptions) {
|
|
88
|
+
const descriptor = Object.getOwnPropertyDescriptor(body, 'toolHandlers');
|
|
89
|
+
if (!descriptor) {
|
|
90
|
+
return { body, options };
|
|
91
|
+
}
|
|
92
|
+
const handlers = body.toolHandlers;
|
|
93
|
+
if (handlers === undefined && 'value' in descriptor) {
|
|
94
|
+
return { body, options };
|
|
95
|
+
}
|
|
96
|
+
const descriptors = Object.getOwnPropertyDescriptors(body);
|
|
97
|
+
delete descriptors.toolHandlers;
|
|
98
|
+
const capturedOptions = { ...options };
|
|
99
|
+
if ('toJSON' in body || capturedOptions.body !== undefined) {
|
|
100
|
+
throw new OpenAIError('Creation tool handlers cannot customize request body serialization');
|
|
101
|
+
}
|
|
102
|
+
if (handlers === undefined) {
|
|
103
|
+
// SAFETY: Preserve request fields while removing the accessor so serialization cannot evaluate it again.
|
|
104
|
+
return {
|
|
105
|
+
body: Object.create(Object.getPrototypeOf(body), descriptors) as SessionCreateParams,
|
|
106
|
+
options: capturedOptions,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
const { stream } = body;
|
|
110
|
+
if (stream !== true) {
|
|
111
|
+
throw new OpenAIError('toolHandlers requires stream: true');
|
|
112
|
+
}
|
|
113
|
+
capturedOptions.headers = buildHeaders([capturedOptions.headers]);
|
|
114
|
+
descriptors.stream = { value: stream, enumerable: true, configurable: true, writable: true };
|
|
115
|
+
// SAFETY: Preserve request fields, omitting callbacks and fixing the validated streaming mode.
|
|
116
|
+
const request = Object.create(Object.getPrototypeOf(body), descriptors) as SessionCreateParams;
|
|
117
|
+
return {
|
|
118
|
+
body: request,
|
|
119
|
+
options: capturedOptions,
|
|
120
|
+
handlers: handlers && Object.fromEntries(Object.entries(handlers)),
|
|
121
|
+
};
|
|
122
|
+
}
|
|
@@ -3,6 +3,8 @@ import type { AgentToolHandler } from '../../agents/agent-session-stream';
|
|
|
3
3
|
import type { AutoParseableResponseTool } from '../../ResponsesParser';
|
|
4
4
|
import type { AgentToolParam } from '../../../resources/beta/agents/agents';
|
|
5
5
|
import { isInputContent } from './tool-output';
|
|
6
|
+
import { toolStages } from './tool-stages';
|
|
7
|
+
import type { StagedToolHandler } from './tool-stages';
|
|
6
8
|
|
|
7
9
|
/** A beta Agents function definition paired with its local, validating handler. */
|
|
8
10
|
export interface AgentFunctionTool {
|
|
@@ -32,6 +34,26 @@ export function functionTool<Arguments>(
|
|
|
32
34
|
if (!execute || !tool.parameters) {
|
|
33
35
|
throw new OpenAIError('Agents function tools require a callback and argument schema');
|
|
34
36
|
}
|
|
37
|
+
const handler: StagedToolHandler = async (arguments_, setStage) => {
|
|
38
|
+
setStage?.('arguments');
|
|
39
|
+
const parsed = parse(JSON.stringify(arguments_));
|
|
40
|
+
setStage?.('execution');
|
|
41
|
+
const result: unknown = await execute(parsed);
|
|
42
|
+
setStage?.('output');
|
|
43
|
+
if (Array.isArray(result)) {
|
|
44
|
+
return result.length > 0 && result.every(isInputContent) ? result : JSON.stringify(result);
|
|
45
|
+
}
|
|
46
|
+
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- Existing schema-tool callbacks return arbitrary application values; adapt them to the Agents dispatcher's output contract.
|
|
47
|
+
if (typeof result === 'object' || typeof result === 'string') {
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
const text = result === undefined ? 'undefined' : JSON.stringify(result);
|
|
51
|
+
if (text === undefined) {
|
|
52
|
+
throw new OpenAIError('Tool output must be JSON serializable');
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
};
|
|
56
|
+
Object.defineProperty(handler, toolStages, { value: true });
|
|
35
57
|
return {
|
|
36
58
|
name: tool.name,
|
|
37
59
|
definition: {
|
|
@@ -41,20 +63,6 @@ export function functionTool<Arguments>(
|
|
|
41
63
|
parameters: tool.parameters,
|
|
42
64
|
...(tool.defer_loading === undefined ? {} : { defer_loading: tool.defer_loading }),
|
|
43
65
|
},
|
|
44
|
-
handler
|
|
45
|
-
const result: unknown = await execute(parse(JSON.stringify(arguments_)));
|
|
46
|
-
if (Array.isArray(result)) {
|
|
47
|
-
return result.length > 0 && result.every(isInputContent) ? result : JSON.stringify(result);
|
|
48
|
-
}
|
|
49
|
-
// oxlint-disable-next-line anti-slop/no-runtime-typeof -- Existing schema-tool callbacks return arbitrary application values; adapt them to the Agents dispatcher's output contract.
|
|
50
|
-
if (typeof result === 'object' || typeof result === 'string') {
|
|
51
|
-
return result;
|
|
52
|
-
}
|
|
53
|
-
const text = result === undefined ? 'undefined' : JSON.stringify(result);
|
|
54
|
-
if (text === undefined) {
|
|
55
|
-
throw new OpenAIError('Tool output must be JSON serializable');
|
|
56
|
-
}
|
|
57
|
-
return text;
|
|
58
|
-
},
|
|
66
|
+
handler,
|
|
59
67
|
};
|
|
60
68
|
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
import { APIUserAbortError, BadRequestError, OpenAIError } from '../../../core/error';
|
|
2
|
+
import { buildHeaders } from '../../../internal/headers';
|
|
3
|
+
import type { RequestOptions } from '../../../internal/request-options';
|
|
4
|
+
import { uuid4 } from '../../../internal/utils/uuid';
|
|
5
|
+
import { isObj } from '../../../internal/utils/values';
|
|
6
|
+
import { isInputContent } from './tool-output';
|
|
7
|
+
import type { AgentToolError } from './tool-error';
|
|
8
|
+
import { toolStages } from './tool-stages';
|
|
9
|
+
import type { StagedToolHandler } from './tool-stages';
|
|
10
|
+
import type {
|
|
11
|
+
AgentToolHandler,
|
|
12
|
+
AgentToolOutput,
|
|
13
|
+
AgentSessionStreamParams,
|
|
14
|
+
} from '../../agents/agent-session-stream';
|
|
15
|
+
import type {
|
|
16
|
+
AgentFunctionCallItem,
|
|
17
|
+
AgentFunctionCallOutputParam,
|
|
18
|
+
AgentSessionInputParam,
|
|
19
|
+
} from '../../../resources/beta/agents/agents';
|
|
20
|
+
import type { Sessions } from '../../../resources/beta/agents/sessions/sessions';
|
|
21
|
+
|
|
22
|
+
type ToolResult = AgentSessionInputParam.SessionInputParamAgentSessionInputToolResult;
|
|
23
|
+
|
|
24
|
+
function normalizedOutput(value: unknown): AgentFunctionCallOutputParam | null {
|
|
25
|
+
if (value === null) {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
if (typeof value === 'string' || (Array.isArray(value) && value.every(isInputContent))) {
|
|
29
|
+
return value;
|
|
30
|
+
}
|
|
31
|
+
throw new OpenAIError('Tool output must be text, content, a JSON object, or null');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// oxlint-disable-next-line anti-slop/no-object-parameters -- The public AgentToolOutput contract accepts arbitrary JSON-serializable object results.
|
|
35
|
+
function toolResult(call: AgentFunctionCallItem, value: AgentToolOutput): ToolResult {
|
|
36
|
+
const output = isObj(value) ? JSON.stringify(value) : value;
|
|
37
|
+
// Detect unserializable callback results inside the redacted failure boundary.
|
|
38
|
+
const serialized = JSON.stringify(output);
|
|
39
|
+
if (serialized === undefined) {
|
|
40
|
+
throw new OpenAIError('Tool output must be JSON serializable');
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
type: 'agent.session.input.tool_result',
|
|
44
|
+
turn_id: call.turn_id,
|
|
45
|
+
call_id: call.call_id,
|
|
46
|
+
success: true,
|
|
47
|
+
output: normalizedOutput(typeof output === 'string' ? output : JSON.parse(serialized)),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Shared local function dispatch for creation and follow-up streams. @internal */
|
|
52
|
+
export class AgentToolDispatcher {
|
|
53
|
+
readonly #handlers: Map<string, AgentToolHandler>;
|
|
54
|
+
readonly #options: RequestOptions;
|
|
55
|
+
readonly #sessions: Sessions;
|
|
56
|
+
readonly #controller: AbortController;
|
|
57
|
+
readonly #onToolError: AgentSessionStreamParams['onToolError'];
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
sessions: Sessions,
|
|
61
|
+
handlers: Record<string, AgentToolHandler>,
|
|
62
|
+
controller: AbortController,
|
|
63
|
+
options?: RequestOptions,
|
|
64
|
+
onToolError?: AgentSessionStreamParams['onToolError'],
|
|
65
|
+
) {
|
|
66
|
+
this.#sessions = sessions;
|
|
67
|
+
this.#controller = controller;
|
|
68
|
+
this.#onToolError = onToolError;
|
|
69
|
+
this.#handlers = new Map(Object.entries(handlers));
|
|
70
|
+
const headers = buildHeaders([options?.headers]);
|
|
71
|
+
headers.values.delete('idempotency-key');
|
|
72
|
+
headers.nulls.delete('idempotency-key');
|
|
73
|
+
const { idempotencyKey: _key, ...rest } = options ?? {};
|
|
74
|
+
this.#options = { ...rest, headers, signal: controller.signal };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
canHandle(name: string): boolean {
|
|
78
|
+
return typeof this.#handlers.get(name) === 'function';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** Capture routing and arguments before the caller can mutate the yielded event. */
|
|
82
|
+
prepare(
|
|
83
|
+
call: AgentFunctionCallItem | undefined,
|
|
84
|
+
sessionID: string | undefined,
|
|
85
|
+
): (() => Promise<void>) | undefined {
|
|
86
|
+
const handler = call && this.#handlers.get(call.name);
|
|
87
|
+
if (!call || typeof handler !== 'function') {
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
if (!sessionID) {
|
|
91
|
+
throw new OpenAIError('Tool call received before the session creation event');
|
|
92
|
+
}
|
|
93
|
+
const snapshot = structuredClone(call);
|
|
94
|
+
return async () => {
|
|
95
|
+
this.#checkAbort();
|
|
96
|
+
const result = await this.#result(snapshot, handler, sessionID);
|
|
97
|
+
this.#checkAbort();
|
|
98
|
+
await this.#submit(sessionID, result);
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async #result(
|
|
103
|
+
call: AgentFunctionCallItem,
|
|
104
|
+
handler: StagedToolHandler,
|
|
105
|
+
sessionID: string,
|
|
106
|
+
): Promise<ToolResult> {
|
|
107
|
+
let stage: AgentToolError['stage'] = 'arguments';
|
|
108
|
+
try {
|
|
109
|
+
const args: unknown = typeof call.arguments === 'string' ? JSON.parse(call.arguments) : call.arguments;
|
|
110
|
+
if (!isObj(args)) {
|
|
111
|
+
throw new OpenAIError('Function arguments must be a JSON object');
|
|
112
|
+
}
|
|
113
|
+
stage = 'execution';
|
|
114
|
+
// SAFETY: Arguments were parsed as JSON and checked to be a non-null non-array object before invoking the handler.
|
|
115
|
+
const arguments_ = args as Record<string, unknown>;
|
|
116
|
+
const output = await this.#wait(() =>
|
|
117
|
+
this.#onToolError && handler[toolStages]
|
|
118
|
+
? handler(arguments_, (value) => {
|
|
119
|
+
stage = value;
|
|
120
|
+
})
|
|
121
|
+
: handler(arguments_),
|
|
122
|
+
);
|
|
123
|
+
stage = 'output';
|
|
124
|
+
return toolResult(call, output);
|
|
125
|
+
} catch (error) {
|
|
126
|
+
this.#checkAbort();
|
|
127
|
+
if (this.#onToolError) {
|
|
128
|
+
const failure: AgentToolError = Object.freeze({
|
|
129
|
+
error,
|
|
130
|
+
stage,
|
|
131
|
+
tool_name: call.name,
|
|
132
|
+
session_id: sessionID,
|
|
133
|
+
turn_id: call.turn_id,
|
|
134
|
+
call_id: call.call_id,
|
|
135
|
+
});
|
|
136
|
+
try {
|
|
137
|
+
await this.#wait(() => this.#onToolError?.(failure));
|
|
138
|
+
} catch {
|
|
139
|
+
// Observers must not prevent the original sanitized tool failure from being submitted.
|
|
140
|
+
this.#checkAbort();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
type: 'agent.session.input.tool_result',
|
|
145
|
+
turn_id: call.turn_id,
|
|
146
|
+
call_id: call.call_id,
|
|
147
|
+
success: false,
|
|
148
|
+
error: 'Tool handler failed.',
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#checkAbort(): void {
|
|
154
|
+
if (this.#controller.signal.aborted) {
|
|
155
|
+
throw this.#abortError();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
#abortError(): APIUserAbortError {
|
|
160
|
+
const error = new APIUserAbortError();
|
|
161
|
+
Object.defineProperty(error, 'cause', {
|
|
162
|
+
value: this.#controller.signal.reason,
|
|
163
|
+
writable: true,
|
|
164
|
+
configurable: true,
|
|
165
|
+
});
|
|
166
|
+
return error;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async #wait<Value>(action: () => Value | PromiseLike<Value>): Promise<Value> {
|
|
170
|
+
let onAbort: (() => void) | undefined;
|
|
171
|
+
// oxlint-disable-next-line promise/avoid-new -- Bridge the caller's AbortSignal while a handler or registration delay is pending.
|
|
172
|
+
const aborted = new Promise<never>((_resolve, reject) => {
|
|
173
|
+
onAbort = () => reject(this.#abortError());
|
|
174
|
+
this.#controller.signal.addEventListener('abort', onAbort, { once: true });
|
|
175
|
+
});
|
|
176
|
+
try {
|
|
177
|
+
this.#checkAbort();
|
|
178
|
+
// Capture synchronous throws before racing cancellation, so both promises
|
|
179
|
+
// always have rejection handlers even if the callback aborts and throws.
|
|
180
|
+
const invoke = async () => await action();
|
|
181
|
+
return await Promise.race([invoke(), aborted]);
|
|
182
|
+
} finally {
|
|
183
|
+
if (onAbort) {
|
|
184
|
+
this.#controller.signal.removeEventListener('abort', onAbort);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async #submit(sessionID: string, result: ToolResult, key = uuid4(), attempt = 0): Promise<void> {
|
|
190
|
+
this.#checkAbort();
|
|
191
|
+
try {
|
|
192
|
+
await this.#sessions.events.create(
|
|
193
|
+
sessionID,
|
|
194
|
+
{ events: [result], 'Idempotency-Key': key },
|
|
195
|
+
this.#options,
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const delay = [100, 300, 600][attempt];
|
|
199
|
+
if (
|
|
200
|
+
delay === undefined ||
|
|
201
|
+
!(error instanceof BadRequestError) ||
|
|
202
|
+
error.code !== 'invalid_request_error' ||
|
|
203
|
+
!error.error ||
|
|
204
|
+
!('message' in error.error) ||
|
|
205
|
+
error.error.message !== `Unknown pending tool call: ${result.call_id}`
|
|
206
|
+
) {
|
|
207
|
+
throw error;
|
|
208
|
+
}
|
|
209
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
210
|
+
try {
|
|
211
|
+
await this.#wait(
|
|
212
|
+
() =>
|
|
213
|
+
// oxlint-disable-next-line promise/avoid-new -- Own the registration timer so cancellation clears it promptly.
|
|
214
|
+
new Promise<void>((resolve) => {
|
|
215
|
+
timer = setTimeout(resolve, delay);
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
} finally {
|
|
219
|
+
if (timer !== undefined) {
|
|
220
|
+
clearTimeout(timer);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
await this.#submit(sessionID, result, key, attempt + 1);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Beta diagnostic for logging or monitoring failures from local `toolHandlers`.
|
|
3
|
+
* The original error is local only; API and transport failures propagate separately.
|
|
4
|
+
*/
|
|
5
|
+
export interface AgentToolError {
|
|
6
|
+
readonly error: unknown;
|
|
7
|
+
readonly tool_name: string;
|
|
8
|
+
readonly session_id: string;
|
|
9
|
+
readonly turn_id: string;
|
|
10
|
+
readonly call_id: string;
|
|
11
|
+
readonly stage: 'arguments' | 'execution' | 'output';
|
|
12
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { AgentToolHandler } from '../../agents/agent-session-stream';
|
|
2
|
+
import type { AgentToolError } from './tool-error';
|
|
3
|
+
|
|
4
|
+
/** @internal */
|
|
5
|
+
export const toolStages = Symbol.for('openai.beta.agents.toolStages');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Marks handlers that accept stage updates, including across ESM/CJS imports.
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export type StagedToolHandler = ((
|
|
12
|
+
arguments_: Parameters<AgentToolHandler>[0],
|
|
13
|
+
setStage?: (stage: AgentToolError['stage']) => void,
|
|
14
|
+
) => ReturnType<AgentToolHandler>) & { [toolStages]?: true };
|