@asap-protocol/openai-agents 2.3.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.
Files changed (46) hide show
  1. package/README.md +53 -0
  2. package/dist/commonjs/asap-as-remote-agent.d.ts +44 -0
  3. package/dist/commonjs/asap-as-remote-agent.d.ts.map +1 -0
  4. package/dist/commonjs/asap-as-remote-agent.js +119 -0
  5. package/dist/commonjs/asap-to-openai-tool.d.ts +34 -0
  6. package/dist/commonjs/asap-to-openai-tool.d.ts.map +1 -0
  7. package/dist/commonjs/asap-to-openai-tool.js +172 -0
  8. package/dist/commonjs/errors.d.ts +27 -0
  9. package/dist/commonjs/errors.d.ts.map +1 -0
  10. package/dist/commonjs/errors.js +53 -0
  11. package/dist/commonjs/index.d.ts +12 -0
  12. package/dist/commonjs/index.d.ts.map +1 -0
  13. package/dist/commonjs/index.js +37 -0
  14. package/dist/commonjs/package.json +3 -0
  15. package/dist/commonjs/schema-bridge.d.ts +8 -0
  16. package/dist/commonjs/schema-bridge.d.ts.map +1 -0
  17. package/dist/commonjs/schema-bridge.js +106 -0
  18. package/dist/commonjs/send-asap-envelope.d.ts +16 -0
  19. package/dist/commonjs/send-asap-envelope.d.ts.map +1 -0
  20. package/dist/commonjs/send-asap-envelope.js +100 -0
  21. package/dist/commonjs/streaming.d.ts +16 -0
  22. package/dist/commonjs/streaming.d.ts.map +1 -0
  23. package/dist/commonjs/streaming.js +45 -0
  24. package/dist/esm/asap-as-remote-agent.d.ts +44 -0
  25. package/dist/esm/asap-as-remote-agent.d.ts.map +1 -0
  26. package/dist/esm/asap-as-remote-agent.js +114 -0
  27. package/dist/esm/asap-to-openai-tool.d.ts +34 -0
  28. package/dist/esm/asap-to-openai-tool.d.ts.map +1 -0
  29. package/dist/esm/asap-to-openai-tool.js +168 -0
  30. package/dist/esm/errors.d.ts +27 -0
  31. package/dist/esm/errors.d.ts.map +1 -0
  32. package/dist/esm/errors.js +44 -0
  33. package/dist/esm/index.d.ts +12 -0
  34. package/dist/esm/index.d.ts.map +1 -0
  35. package/dist/esm/index.js +11 -0
  36. package/dist/esm/package.json +3 -0
  37. package/dist/esm/schema-bridge.d.ts +8 -0
  38. package/dist/esm/schema-bridge.d.ts.map +1 -0
  39. package/dist/esm/schema-bridge.js +103 -0
  40. package/dist/esm/send-asap-envelope.d.ts +16 -0
  41. package/dist/esm/send-asap-envelope.d.ts.map +1 -0
  42. package/dist/esm/send-asap-envelope.js +97 -0
  43. package/dist/esm/streaming.d.ts +16 -0
  44. package/dist/esm/streaming.d.ts.map +1 -0
  45. package/dist/esm/streaming.js +41 -0
  46. package/package.json +69 -0
@@ -0,0 +1,106 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.zodFromJsonSchema = zodFromJsonSchema;
4
+ const zod_1 = require("zod");
5
+ function enumLiteralKind(value) {
6
+ if (value === null) {
7
+ return "null";
8
+ }
9
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
10
+ return typeof value;
11
+ }
12
+ return "unsupported";
13
+ }
14
+ function zodFromJsonSchemaEnum(enumValues) {
15
+ if (enumValues.length === 0) {
16
+ throw new Error("JSON Schema enum must contain at least one value");
17
+ }
18
+ const kinds = new Set();
19
+ for (const value of enumValues) {
20
+ kinds.add(enumLiteralKind(value));
21
+ }
22
+ if (kinds.has("unsupported")) {
23
+ throw new Error("JSON Schema enum supports only string, number, boolean, and null literals");
24
+ }
25
+ const nonNullKinds = [...kinds].filter((kind) => kind !== "null");
26
+ if (nonNullKinds.length > 1) {
27
+ throw new Error(`JSON Schema enum with mixed primitive types is unsupported (found: ${[...kinds].join(", ")})`);
28
+ }
29
+ const literals = enumValues.map((value) => {
30
+ if (value === null) {
31
+ return zod_1.z.null();
32
+ }
33
+ if (typeof value === "string") {
34
+ return zod_1.z.literal(value);
35
+ }
36
+ if (typeof value === "number") {
37
+ return zod_1.z.literal(value);
38
+ }
39
+ return zod_1.z.literal(value);
40
+ });
41
+ return literals.length === 1
42
+ ? literals[0]
43
+ : zod_1.z.union(literals);
44
+ }
45
+ /**
46
+ * Minimal JSON Schema → Zod bridge for adapter tool schemas (subset only).
47
+ *
48
+ * Lossy for `$ref` / complex `oneOf`; mirrors `@asap-protocol/mastra` / legacy Chat Completions adapter patterns.
49
+ */
50
+ function zodFromJsonSchema(schema) {
51
+ if ("$ref" in schema) {
52
+ return zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
53
+ }
54
+ if ("oneOf" in schema && Array.isArray(schema.oneOf)) {
55
+ const branches = schema.oneOf
56
+ .filter((s) => typeof s === "object" && s !== null && !Array.isArray(s))
57
+ .map((s) => zodFromJsonSchema(s));
58
+ return branches.length === 0
59
+ ? zod_1.z.unknown()
60
+ : branches.length === 1
61
+ ? branches[0]
62
+ : zod_1.z.union(branches);
63
+ }
64
+ if ("enum" in schema && Array.isArray(schema.enum) && schema.enum.length > 0) {
65
+ return zodFromJsonSchemaEnum(schema.enum);
66
+ }
67
+ switch (schema.type) {
68
+ case "object": {
69
+ const maybeProps = schema.properties;
70
+ if (maybeProps !== undefined &&
71
+ typeof maybeProps === "object" &&
72
+ maybeProps !== null &&
73
+ !Array.isArray(maybeProps)) {
74
+ const propsRecord = maybeProps;
75
+ const required = new Set(Array.isArray(schema.required) ? schema.required.filter((k) => typeof k === "string") : []);
76
+ const shape = {};
77
+ for (const [key, val] of Object.entries(propsRecord)) {
78
+ if (typeof val === "object" && val !== null && !Array.isArray(val)) {
79
+ const child = zodFromJsonSchema(val);
80
+ shape[key] = required.has(key) ? child : child.optional();
81
+ }
82
+ }
83
+ if (Object.keys(shape).length > 0) {
84
+ return zod_1.z.object(shape);
85
+ }
86
+ }
87
+ return zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
88
+ }
89
+ case "string":
90
+ return zod_1.z.string();
91
+ case "number":
92
+ case "integer":
93
+ return zod_1.z.number();
94
+ case "boolean":
95
+ return zod_1.z.boolean();
96
+ case "array": {
97
+ const items = schema.items;
98
+ if (typeof items === "object" && items !== null && !Array.isArray(items)) {
99
+ return zod_1.z.array(zodFromJsonSchema(items));
100
+ }
101
+ return zod_1.z.array(zod_1.z.unknown());
102
+ }
103
+ default:
104
+ return zod_1.z.record(zod_1.z.string(), zod_1.z.unknown());
105
+ }
106
+ }
@@ -0,0 +1,16 @@
1
+ export type SendAsapFetch = typeof fetch;
2
+ export interface SendAsapEnvelopeOptions {
3
+ readonly fetch?: SendAsapFetch;
4
+ readonly signal?: AbortSignal;
5
+ /** When set, sends `Authorization: Bearer <token>`. */
6
+ readonly agentJwt?: string;
7
+ /** Sent as `ASAP-Version` (default `2.2`). */
8
+ readonly asapVersion?: string;
9
+ }
10
+ /**
11
+ * POST a JSON-RPC `asap.send` request to the provider's `/asap` endpoint.
12
+ *
13
+ * @throws When HTTP status is not ok or the JSON-RPC response contains an error.
14
+ */
15
+ export declare function sendAsapEnvelope(provider: URL, envelope: Record<string, unknown>, options?: SendAsapEnvelopeOptions): Promise<unknown>;
16
+ //# sourceMappingURL=send-asap-envelope.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"send-asap-envelope.d.ts","sourceRoot":"","sources":["../../src/send-asap-envelope.ts"],"names":[],"mappings":"AAIA,MAAM,MAAM,aAAa,GAAG,OAAO,KAAK,CAAC;AAEzC,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC;IAC/B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,uDAAuD;IACvD,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC/B;AAiED;;;;GAIG;AACH,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,GAAG,EACb,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACjC,OAAO,CAAC,EAAE,uBAAuB,GAChC,OAAO,CAAC,OAAO,CAAC,CAqClB"}
@@ -0,0 +1,100 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sendAsapEnvelope = sendAsapEnvelope;
4
+ const client_1 = require("@asap-protocol/client");
5
+ const DEFAULT_ASAP_VERSION = "2.2";
6
+ /** Base URL with trailing slash for safe relative resolution of `asap/...` paths. */
7
+ function providerBaseHref(provider) {
8
+ const href = provider.href;
9
+ return provider.pathname.endsWith("/") ? href : `${href}/`;
10
+ }
11
+ function asapSendHref(provider) {
12
+ return new URL("asap", providerBaseHref(provider)).href;
13
+ }
14
+ function isRecord(value) {
15
+ return typeof value === "object" && value !== null && !Array.isArray(value);
16
+ }
17
+ function jsonRpcErrorToError(error) {
18
+ if (!isRecord(error)) {
19
+ return new Error(String(error));
20
+ }
21
+ const message = typeof error.message === "string" ? error.message : JSON.stringify(error);
22
+ const err = new Error(message);
23
+ err.name = "JsonRpcError";
24
+ if (typeof error.code === "number") {
25
+ err.code = error.code;
26
+ }
27
+ return err;
28
+ }
29
+ function parseJsonRpcResult(payload) {
30
+ if (!isRecord(payload)) {
31
+ return payload;
32
+ }
33
+ if (payload.jsonrpc !== "2.0") {
34
+ return payload;
35
+ }
36
+ if (payload.error !== undefined && payload.error !== null) {
37
+ throw jsonRpcErrorToError(payload.error);
38
+ }
39
+ return payload.result;
40
+ }
41
+ function normalizeEnvelopeForSend(envelope) {
42
+ return {
43
+ ...envelope,
44
+ id: typeof envelope.id === "string" ? envelope.id : crypto.randomUUID(),
45
+ timestamp: typeof envelope.timestamp === "string" ? envelope.timestamp : new Date().toISOString(),
46
+ };
47
+ }
48
+ function buildJsonRpcSendBody(envelope) {
49
+ const requestId = crypto.randomUUID();
50
+ const idempotencyKey = crypto.randomUUID();
51
+ const jsonRpcRequest = {
52
+ jsonrpc: "2.0",
53
+ method: client_1.ASAP_SEND_METHOD,
54
+ params: {
55
+ envelope,
56
+ idempotency_key: idempotencyKey,
57
+ },
58
+ id: requestId,
59
+ };
60
+ return { body: JSON.stringify(jsonRpcRequest), idempotencyKey };
61
+ }
62
+ /**
63
+ * POST a JSON-RPC `asap.send` request to the provider's `/asap` endpoint.
64
+ *
65
+ * @throws When HTTP status is not ok or the JSON-RPC response contains an error.
66
+ */
67
+ async function sendAsapEnvelope(provider, envelope, options) {
68
+ const fetchFn = options?.fetch ?? globalThis.fetch.bind(globalThis);
69
+ const asapVersion = options?.asapVersion ?? DEFAULT_ASAP_VERSION;
70
+ const wireEnvelope = normalizeEnvelopeForSend(envelope);
71
+ const { body, idempotencyKey } = buildJsonRpcSendBody(wireEnvelope);
72
+ const headers = {
73
+ "Content-Type": "application/json",
74
+ Accept: "application/json",
75
+ "X-Idempotency-Key": idempotencyKey,
76
+ [client_1.ASAP_VERSION_HEADER]: asapVersion,
77
+ };
78
+ if (options?.agentJwt !== undefined && options.agentJwt.length > 0) {
79
+ headers.Authorization = `Bearer ${options.agentJwt}`;
80
+ }
81
+ const response = await fetchFn(asapSendHref(provider), {
82
+ method: "POST",
83
+ headers,
84
+ body,
85
+ signal: options?.signal,
86
+ });
87
+ const text = await response.text();
88
+ let parsed;
89
+ try {
90
+ parsed = text.length === 0 ? {} : JSON.parse(text);
91
+ }
92
+ catch (e) {
93
+ const msg = e instanceof Error ? e.message : String(e);
94
+ throw new Error(`asap.send: invalid JSON (${msg})`);
95
+ }
96
+ if (!response.ok) {
97
+ throw new Error(`asap.send failed: HTTP ${String(response.status)} ${text.slice(0, 200)}`);
98
+ }
99
+ return parseJsonRpcResult(parsed);
100
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Bridge ASAP TaskStream-style payloads into UTF-8 text chunks suitable for OpenAI Agents streaming UX.
3
+ *
4
+ * Pair with `@asap-protocol/client` streaming helpers (`createAsapStreamClient`, `streamTaskStreamEnvelopes`).
5
+ */
6
+ /** Streaming text delta aligned with incremental assistant output (surface-only shape). */
7
+ export interface OpenAIAgentsStreamTextDelta {
8
+ readonly type: "text_delta";
9
+ readonly text: string;
10
+ }
11
+ export declare function asapStreamToOpenAIAgentsTextStream(source: AsyncIterable<unknown>): AsyncIterable<string>;
12
+ /**
13
+ * Same mapping as {@link asapStreamToOpenAIAgentsTextStream}, wrapped as `{ type: "text_delta" }` chunks.
14
+ */
15
+ export declare function asapStreamToOpenAIAgentsRunStreamChunks(source: AsyncIterable<unknown>): AsyncIterable<OpenAIAgentsStreamTextDelta>;
16
+ //# sourceMappingURL=streaming.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streaming.d.ts","sourceRoot":"","sources":["../../src/streaming.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,2FAA2F;AAC3F,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,wBAAuB,kCAAkC,CAAC,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAAG,aAAa,CAAC,MAAM,CAAC,CAuB/G;AAED;;GAEG;AACH,wBAAuB,uCAAuC,CAC5D,MAAM,EAAE,aAAa,CAAC,OAAO,CAAC,GAC7B,aAAa,CAAC,2BAA2B,CAAC,CAI5C"}
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ /**
3
+ * Bridge ASAP TaskStream-style payloads into UTF-8 text chunks suitable for OpenAI Agents streaming UX.
4
+ *
5
+ * Pair with `@asap-protocol/client` streaming helpers (`createAsapStreamClient`, `streamTaskStreamEnvelopes`).
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.asapStreamToOpenAIAgentsTextStream = asapStreamToOpenAIAgentsTextStream;
9
+ exports.asapStreamToOpenAIAgentsRunStreamChunks = asapStreamToOpenAIAgentsRunStreamChunks;
10
+ function isRecord(x) {
11
+ return typeof x === "object" && x !== null && !Array.isArray(x);
12
+ }
13
+ async function* asapStreamToOpenAIAgentsTextStream(source) {
14
+ const it = source[Symbol.asyncIterator]();
15
+ try {
16
+ while (true) {
17
+ const { value: event, done } = await it.next();
18
+ if (done) {
19
+ return;
20
+ }
21
+ if (!isRecord(event) || event.type !== "task_stream") {
22
+ continue;
23
+ }
24
+ const payload = event.payload;
25
+ if (!isRecord(payload)) {
26
+ continue;
27
+ }
28
+ const chunk = payload.chunk;
29
+ if (typeof chunk === "string") {
30
+ yield chunk;
31
+ }
32
+ }
33
+ }
34
+ finally {
35
+ await it.return?.();
36
+ }
37
+ }
38
+ /**
39
+ * Same mapping as {@link asapStreamToOpenAIAgentsTextStream}, wrapped as `{ type: "text_delta" }` chunks.
40
+ */
41
+ async function* asapStreamToOpenAIAgentsRunStreamChunks(source) {
42
+ for await (const text of asapStreamToOpenAIAgentsTextStream(source)) {
43
+ yield { type: "text_delta", text };
44
+ }
45
+ }
@@ -0,0 +1,44 @@
1
+ import { Agent, type AgentInputItem } from "@openai/agents";
2
+ import type { AsapExecuteClient } from "@asap-protocol/client/adapters/shared";
3
+ import { type AsapToolsForOpenAIAgentsOptions } from "./asap-to-openai-tool.js";
4
+ export type AsapRemoteAgentMode = "delegated" | "autonomous";
5
+ /** Mutable bag carried through {@link RunContext} when using {@link asapAsRemoteAgent}. */
6
+ export interface AsapRemoteRunContext {
7
+ /** Latest draft ASAP envelope captured when this agent starts (including after an SDK handoff). */
8
+ lastAsapHandoffEnvelope?: Record<string, unknown>;
9
+ }
10
+ export interface AsapAsRemoteAgentOptions {
11
+ readonly mode?: AsapRemoteAgentMode;
12
+ readonly name?: string;
13
+ readonly instructions?: string | undefined;
14
+ readonly handoffDescription?: string;
15
+ readonly model?: string;
16
+ readonly toolsOptions?: AsapToolsForOpenAIAgentsOptions;
17
+ /**
18
+ * Agent JWT scoped to `providerUrl` when it differs from `client.provider`.
19
+ * Required for cross-provider handoffs when `client.agentJwt` is set.
20
+ */
21
+ readonly remoteAgentJwt?: string;
22
+ }
23
+ /**
24
+ * Returns whether two provider URLs refer to the same ASAP host path (origin + pathname).
25
+ */
26
+ export declare function sameProviderOrigin(a: URL, b: URL): boolean;
27
+ /**
28
+ * Draft `task.request`-style envelope metadata used when delegating from OpenAI Agents handoffs to ASAP.
29
+ *
30
+ * The gateway still performs authorization and routing; this object mirrors how adapters attach agent mode.
31
+ */
32
+ export declare function draftTaskRequestEnvelopeForRemoteAgent(params: {
33
+ readonly mode: AsapRemoteAgentMode;
34
+ readonly providerUrl: URL;
35
+ readonly turnInput?: string | AgentInputItem[];
36
+ }): Record<string, unknown>;
37
+ /**
38
+ * Returns an OpenAI Agents {@link Agent} backed by ASAP capability tools on `providerUrl`.
39
+ *
40
+ * Subscribes to `agent_start` so {@link AsapRemoteRunContext.lastAsapHandoffEnvelope} captures draft envelope metadata
41
+ * and POSTs a real `task.request` via JSON-RPC `asap.send` whenever this agent begins a turn—including after an SDK handoff.
42
+ */
43
+ export declare function asapAsRemoteAgent(client: AsapExecuteClient, providerUrl: string | URL, options?: AsapAsRemoteAgentOptions): Promise<Agent<AsapRemoteRunContext>>;
44
+ //# sourceMappingURL=asap-as-remote-agent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asap-as-remote-agent.d.ts","sourceRoot":"","sources":["../../src/asap-as-remote-agent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE5D,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,uCAAuC,CAAC;AAE/E,OAAO,EACL,KAAK,+BAA+B,EAErC,MAAM,0BAA0B,CAAC;AAGlC,MAAM,MAAM,mBAAmB,GAAG,WAAW,GAAG,YAAY,CAAC;AAE7D,2FAA2F;AAC3F,MAAM,WAAW,oBAAoB;IACnC,mGAAmG;IACnG,uBAAuB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnD;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,IAAI,CAAC,EAAE,mBAAmB,CAAC;IACpC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,YAAY,CAAC,EAAE,+BAA+B,CAAC;IACxD;;;OAGG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CAClC;AAgBD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,OAAO,CAE1D;AAED;;;;GAIG;AACH,wBAAgB,sCAAsC,CAAC,MAAM,EAAE;IAC7D,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,GAAG,CAAC;IAC1B,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,cAAc,EAAE,CAAC;CAChD,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAmB1B;AAgDD;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,MAAM,EAAE,iBAAiB,EACzB,WAAW,EAAE,MAAM,GAAG,GAAG,EACzB,OAAO,CAAC,EAAE,wBAAwB,GACjC,OAAO,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAgCtC"}
@@ -0,0 +1,114 @@
1
+ import { Agent } from "@openai/agents";
2
+ import { asapToolsForOpenAIAgents, } from "./asap-to-openai-tool.js";
3
+ import { sendAsapEnvelope } from "./send-asap-envelope.js";
4
+ function serializeTurnInput(turnInput) {
5
+ if (turnInput === undefined) {
6
+ return "";
7
+ }
8
+ if (typeof turnInput === "string") {
9
+ return turnInput;
10
+ }
11
+ try {
12
+ return JSON.stringify(turnInput);
13
+ }
14
+ catch {
15
+ return "";
16
+ }
17
+ }
18
+ /**
19
+ * Returns whether two provider URLs refer to the same ASAP host path (origin + pathname).
20
+ */
21
+ export function sameProviderOrigin(a, b) {
22
+ return a.origin === b.origin && a.pathname === b.pathname;
23
+ }
24
+ /**
25
+ * Draft `task.request`-style envelope metadata used when delegating from OpenAI Agents handoffs to ASAP.
26
+ *
27
+ * The gateway still performs authorization and routing; this object mirrors how adapters attach agent mode.
28
+ */
29
+ export function draftTaskRequestEnvelopeForRemoteAgent(params) {
30
+ const delegatedInput = serializeTurnInput(params.turnInput);
31
+ return {
32
+ asap_version: "2.2",
33
+ sender: "urn:asap:agent:openai-agents-adapter",
34
+ recipient: params.providerUrl.href,
35
+ payload_type: "task.request",
36
+ payload: {
37
+ conversation_id: "openai-agents-handoff",
38
+ skill_id: "asap-remote-handoff",
39
+ input: {
40
+ query: delegatedInput,
41
+ asap_agent_mode: params.mode,
42
+ },
43
+ },
44
+ extensions: {
45
+ asap_agent_mode: params.mode,
46
+ },
47
+ };
48
+ }
49
+ function defaultInstructions(mode, capabilities) {
50
+ const list = capabilities.length > 0 ? capabilities.join(", ") : "(none configured)";
51
+ return [
52
+ "You are an ASAP-backed specialist agent invoked via OpenAI Agents SDK handoffs.",
53
+ `Operate in ${mode} mode with respect to host policy.`,
54
+ `Use the ASAP capability tools wired for this agent (deterministic keys). Capability URNs: ${list}.`,
55
+ "Pass structured JSON arguments that satisfy each capability schema.",
56
+ ].join(" ");
57
+ }
58
+ function resolveProvider(providerUrl) {
59
+ return typeof providerUrl === "string" ? new URL(providerUrl) : providerUrl;
60
+ }
61
+ function resolveHandoffJwt(client, providerUrl, options) {
62
+ if (options?.remoteAgentJwt !== undefined) {
63
+ return options.remoteAgentJwt;
64
+ }
65
+ if (client.agentJwt !== undefined && !sameProviderOrigin(client.provider, providerUrl)) {
66
+ throw new Error(`client.agentJwt is scoped to ${client.provider.href}; pass remoteAgentJwt when providerUrl (${providerUrl.href}) differs`);
67
+ }
68
+ return client.agentJwt;
69
+ }
70
+ function buildMergedClient(client, resolvedProvider, handoffJwt) {
71
+ const merged = {
72
+ ...client,
73
+ provider: resolvedProvider,
74
+ };
75
+ if (handoffJwt !== undefined) {
76
+ return { ...merged, agentJwt: handoffJwt };
77
+ }
78
+ const { agentJwt: _omit, ...withoutJwt } = merged;
79
+ return withoutJwt;
80
+ }
81
+ /**
82
+ * Returns an OpenAI Agents {@link Agent} backed by ASAP capability tools on `providerUrl`.
83
+ *
84
+ * Subscribes to `agent_start` so {@link AsapRemoteRunContext.lastAsapHandoffEnvelope} captures draft envelope metadata
85
+ * and POSTs a real `task.request` via JSON-RPC `asap.send` whenever this agent begins a turn—including after an SDK handoff.
86
+ */
87
+ export async function asapAsRemoteAgent(client, providerUrl, options) {
88
+ const mode = options?.mode ?? "delegated";
89
+ const resolvedProvider = resolveProvider(providerUrl);
90
+ const handoffJwt = resolveHandoffJwt(client, resolvedProvider, options);
91
+ const mergedClient = buildMergedClient(client, resolvedProvider, handoffJwt);
92
+ const tools = await asapToolsForOpenAIAgents(mergedClient, options?.toolsOptions);
93
+ const agent = new Agent({
94
+ name: options?.name ?? `asap-remote-${resolvedProvider.hostname}`,
95
+ instructions: options?.instructions ?? defaultInstructions(mode, mergedClient.capabilities),
96
+ handoffDescription: options?.handoffDescription ??
97
+ `Delegates to ASAP capabilities at ${resolvedProvider.href} (${mode} mode).`,
98
+ model: options?.model ?? "gpt-4o-mini",
99
+ tools: [...tools],
100
+ });
101
+ agent.on("agent_start", (runContext, _self, turnInput) => {
102
+ const draft = draftTaskRequestEnvelopeForRemoteAgent({
103
+ mode,
104
+ providerUrl: resolvedProvider,
105
+ turnInput,
106
+ });
107
+ runContext.context.lastAsapHandoffEnvelope = draft;
108
+ void sendAsapEnvelope(resolvedProvider, draft, {
109
+ fetch: mergedClient.fetch,
110
+ agentJwt: mergedClient.agentJwt,
111
+ });
112
+ });
113
+ return agent;
114
+ }
@@ -0,0 +1,34 @@
1
+ import { tool } from "@openai/agents";
2
+ import { type AsapExecuteClient } from "@asap-protocol/client/adapters/shared";
3
+ export interface AsapToolsForOpenAIAgentsOptions {
4
+ /**
5
+ * Invoked when the provider returns `403` with `error.code === "capability_not_granted"`.
6
+ */
7
+ readonly requestCapability?: (requiredCapability: string) => void | Promise<void>;
8
+ readonly inputSchemas?: Readonly<Record<string, unknown>>;
9
+ readonly outputSchemas?: Readonly<Record<string, unknown>>;
10
+ /**
11
+ * Called when {@link describeCapability} fails for a capability. The error is rethrown unless
12
+ * {@link allowPermissiveDescribeFallback} is `true`.
13
+ */
14
+ readonly onDescribeError?: (capabilityId: string, error: unknown) => void;
15
+ /**
16
+ * When `true`, failed describe calls fall back to generic tool metadata instead of failing
17
+ * tool construction. Defaults to `false`.
18
+ */
19
+ readonly allowPermissiveDescribeFallback?: boolean;
20
+ /** Passed to {@link describeCapability} and {@link executeCapability} fetch calls. */
21
+ readonly signal?: AbortSignal;
22
+ }
23
+ /**
24
+ * Build OpenAI Agents SDK {@link tool} instances for each ASAP capability on the client.
25
+ *
26
+ * Uses top-level {@link executeCapability} from `@asap-protocol/client` on each execution path.
27
+ */
28
+ export declare function asapToolsForOpenAIAgents(client: AsapExecuteClient, options?: AsapToolsForOpenAIAgentsOptions): Promise<readonly ReturnType<typeof tool>[]>;
29
+ /**
30
+ * Synchronous variant when {@link AsapToolsForOpenAIAgentsOptions.inputSchemas} / `outputSchemas`
31
+ * are already materialized (skips describe round-trips).
32
+ */
33
+ export declare function asapToolsForOpenAIAgentsSync(client: AsapExecuteClient, options?: AsapToolsForOpenAIAgentsOptions): readonly ReturnType<typeof tool>[];
34
+ //# sourceMappingURL=asap-to-openai-tool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"asap-to-openai-tool.d.ts","sourceRoot":"","sources":["../../src/asap-to-openai-tool.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAEtC,OAAO,EAGL,KAAK,iBAAiB,EACvB,MAAM,uCAAuC,CAAC;AAiC/C,MAAM,WAAW,+BAA+B;IAC9C;;OAEG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,kBAAkB,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClF,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,aAAa,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IAC3D;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;IAC1E;;;OAGG;IACH,QAAQ,CAAC,+BAA+B,CAAC,EAAE,OAAO,CAAC;IACnD,sFAAsF;IACtF,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAuID;;;;GAIG;AACH,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,+BAA+B,GACxC,OAAO,CAAC,SAAS,UAAU,CAAC,OAAO,IAAI,CAAC,EAAE,CAAC,CA4C7C;AAED;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,iBAAiB,EACzB,OAAO,CAAC,EAAE,+BAA+B,GACxC,SAAS,UAAU,CAAC,OAAO,IAAI,CAAC,EAAE,CAepC"}
@@ -0,0 +1,168 @@
1
+ import { tool } from "@openai/agents";
2
+ import { describeCapability, executeCapability } from "@asap-protocol/client";
3
+ import { capabilityToolKey, jsonSchemaForCapabilityInput, } from "@asap-protocol/client/adapters/shared";
4
+ import { z } from "zod";
5
+ import { ApprovalRequiredError, CapabilityNotGrantedError } from "./errors.js";
6
+ import { zodFromJsonSchema } from "./schema-bridge.js";
7
+ function buildToolParameters(inputJson) {
8
+ const props = inputJson.properties;
9
+ const hasProps = inputJson.type === "object" &&
10
+ typeof props === "object" &&
11
+ props !== null &&
12
+ !Array.isArray(props) &&
13
+ Object.keys(props).length > 0;
14
+ if (hasProps) {
15
+ const zSch = zodFromJsonSchema(inputJson);
16
+ if (zSch instanceof z.ZodObject) {
17
+ return { mode: "zod", schema: zSch.strict() };
18
+ }
19
+ }
20
+ return { mode: "json", schema: inputJson };
21
+ }
22
+ const DESCRIBE_CONCURRENCY = 5;
23
+ async function mapInBatches(items, batchSize, fn) {
24
+ const results = [];
25
+ for (let i = 0; i < items.length; i += batchSize) {
26
+ const batch = items.slice(i, i + batchSize);
27
+ const batchResults = await Promise.all(batch.map(fn));
28
+ results.push(...batchResults);
29
+ }
30
+ return results;
31
+ }
32
+ function isRecord(x) {
33
+ return typeof x === "object" && x !== null && !Array.isArray(x);
34
+ }
35
+ function wrapFetchWithCapabilityErrors(base, options) {
36
+ const impl = base ?? globalThis.fetch;
37
+ return async (input, init) => {
38
+ const res = await impl(input, init);
39
+ if (res.status !== 403) {
40
+ return res;
41
+ }
42
+ const text = await res.clone().text();
43
+ let parsed;
44
+ try {
45
+ parsed = text.length === 0 ? {} : JSON.parse(text);
46
+ }
47
+ catch {
48
+ return new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers });
49
+ }
50
+ if (!isRecord(parsed)) {
51
+ return new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers });
52
+ }
53
+ if (parsed.error === "constraint_violated") {
54
+ return new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers });
55
+ }
56
+ const errObj = parsed.error;
57
+ if (!isRecord(errObj)) {
58
+ return new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers });
59
+ }
60
+ const code = errObj.code;
61
+ if (code === "capability_not_granted") {
62
+ const data = isRecord(errObj.data) ? errObj.data : undefined;
63
+ const required = typeof data?.required_capability === "string" ? data.required_capability : "";
64
+ const msg = typeof errObj.message === "string" ? errObj.message : undefined;
65
+ throw new CapabilityNotGrantedError(required, options?.requestCapability, msg);
66
+ }
67
+ if (code === "approval_required") {
68
+ const msg = typeof errObj.message === "string" ? errObj.message : "Capability execution requires approval";
69
+ throw new ApprovalRequiredError(msg, errObj.data);
70
+ }
71
+ return new Response(text, { status: res.status, statusText: res.statusText, headers: res.headers });
72
+ };
73
+ }
74
+ function buildTools(client, fetchFn, describedByCapability, options) {
75
+ const tools = [];
76
+ const seenToolNames = new Map();
77
+ for (const capabilityId of client.capabilities) {
78
+ const id = capabilityToolKey(capabilityId);
79
+ const existingCapabilityId = seenToolNames.get(id);
80
+ if (existingCapabilityId !== undefined && existingCapabilityId !== capabilityId) {
81
+ throw new Error(`OpenAI Agents tool name collision: "${id}" maps to both "${existingCapabilityId}" and "${capabilityId}". ` +
82
+ "Use distinct capability identifiers or provide inputSchemas to skip describe.");
83
+ }
84
+ seenToolNames.set(id, capabilityId);
85
+ const described = describedByCapability.get(capabilityId);
86
+ const inputJson = jsonSchemaForCapabilityInput(options?.inputSchemas?.[capabilityId] ?? described?.input_schema);
87
+ const built = buildToolParameters(inputJson);
88
+ const exec = async (input) => {
89
+ const ctx = isRecord(input) ? input : {};
90
+ return executeCapability(client.provider, capabilityId, ctx, {
91
+ agentJwt: client.agentJwt,
92
+ fetch: fetchFn,
93
+ signal: options?.signal,
94
+ });
95
+ };
96
+ if (built.mode === "zod") {
97
+ tools.push(tool({
98
+ name: id,
99
+ description: described?.description ?? `ASAP capability: ${capabilityId}`,
100
+ parameters: built.schema,
101
+ strict: true,
102
+ errorFunction: null,
103
+ execute: exec,
104
+ }));
105
+ }
106
+ else {
107
+ tools.push(tool({
108
+ name: id,
109
+ description: described?.description ?? `ASAP capability: ${capabilityId}`,
110
+ parameters: built.schema,
111
+ strict: false,
112
+ errorFunction: null,
113
+ execute: exec,
114
+ }));
115
+ }
116
+ }
117
+ return tools;
118
+ }
119
+ /**
120
+ * Build OpenAI Agents SDK {@link tool} instances for each ASAP capability on the client.
121
+ *
122
+ * Uses top-level {@link executeCapability} from `@asap-protocol/client` on each execution path.
123
+ */
124
+ export async function asapToolsForOpenAIAgents(client, options) {
125
+ const fetchFn = wrapFetchWithCapabilityErrors(client.fetch, options);
126
+ const describedByCapability = new Map();
127
+ const toDescribe = client.capabilities.filter((capabilityId) => options?.inputSchemas?.[capabilityId] === undefined);
128
+ for (const capabilityId of client.capabilities) {
129
+ if (options?.inputSchemas?.[capabilityId] !== undefined) {
130
+ describedByCapability.set(capabilityId, undefined);
131
+ }
132
+ }
133
+ await mapInBatches(toDescribe, DESCRIBE_CONCURRENCY, async (capabilityId) => {
134
+ try {
135
+ const described = await describeCapability(client.provider, capabilityId, {
136
+ fetch: fetchFn,
137
+ agentJwt: client.agentJwt,
138
+ signal: options?.signal,
139
+ });
140
+ describedByCapability.set(capabilityId, {
141
+ description: described.description,
142
+ input_schema: described.input_schema,
143
+ output_schema: described.output_schema,
144
+ });
145
+ }
146
+ catch (error) {
147
+ options?.onDescribeError?.(capabilityId, error);
148
+ if (options?.allowPermissiveDescribeFallback === true) {
149
+ describedByCapability.set(capabilityId, undefined);
150
+ return;
151
+ }
152
+ throw error;
153
+ }
154
+ });
155
+ return buildTools(client, fetchFn, describedByCapability, options);
156
+ }
157
+ /**
158
+ * Synchronous variant when {@link AsapToolsForOpenAIAgentsOptions.inputSchemas} / `outputSchemas`
159
+ * are already materialized (skips describe round-trips).
160
+ */
161
+ export function asapToolsForOpenAIAgentsSync(client, options) {
162
+ const fetchFn = wrapFetchWithCapabilityErrors(client.fetch, options);
163
+ const describedByCapability = new Map();
164
+ for (const capabilityId of client.capabilities) {
165
+ describedByCapability.set(capabilityId, undefined);
166
+ }
167
+ return buildTools(client, fetchFn, describedByCapability, options);
168
+ }