@finalai/agents 0.8.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.
Potentially problematic release.
This version of @finalai/agents might be problematic. Click here for more details.
- package/README.md +30 -0
- package/dist/modules/agent-client/acp-frames.d.mts +19 -0
- package/dist/modules/agent-client/acp-frames.mjs +49 -0
- package/dist/modules/agent-client/acp-frames.mjs.map +7 -0
- package/dist/modules/agent-client/acp.d.mts +43 -0
- package/dist/modules/agent-client/acp.mjs +76 -0
- package/dist/modules/agent-client/acp.mjs.map +7 -0
- package/dist/modules/agent-client/async-queue.d.mts +10 -0
- package/dist/modules/agent-client/async-queue.mjs +66 -0
- package/dist/modules/agent-client/async-queue.mjs.map +7 -0
- package/dist/modules/agent-client/build.d.mts +107 -0
- package/dist/modules/agent-client/build.mjs +222 -0
- package/dist/modules/agent-client/build.mjs.map +7 -0
- package/dist/modules/agent-client/client.d.mts +111 -0
- package/dist/modules/agent-client/client.mjs +198 -0
- package/dist/modules/agent-client/client.mjs.map +7 -0
- package/dist/modules/agent-client/collection-events.d.mts +35 -0
- package/dist/modules/agent-client/collection-events.mjs +142 -0
- package/dist/modules/agent-client/collection-events.mjs.map +7 -0
- package/dist/modules/agent-client/config-options.d.mts +24 -0
- package/dist/modules/agent-client/config-options.mjs +49 -0
- package/dist/modules/agent-client/config-options.mjs.map +7 -0
- package/dist/modules/agent-client/context.d.mts +31 -0
- package/dist/modules/agent-client/context.mjs +11 -0
- package/dist/modules/agent-client/context.mjs.map +7 -0
- package/dist/modules/agent-client/conversations.d.mts +385 -0
- package/dist/modules/agent-client/conversations.mjs +1156 -0
- package/dist/modules/agent-client/conversations.mjs.map +7 -0
- package/dist/modules/agent-client/errors.d.mts +85 -0
- package/dist/modules/agent-client/errors.mjs +94 -0
- package/dist/modules/agent-client/errors.mjs.map +7 -0
- package/dist/modules/agent-client/event-stream.d.mts +14 -0
- package/dist/modules/agent-client/event-stream.mjs +89 -0
- package/dist/modules/agent-client/event-stream.mjs.map +7 -0
- package/dist/modules/agent-client/frame-stream.d.mts +51 -0
- package/dist/modules/agent-client/frame-stream.mjs +333 -0
- package/dist/modules/agent-client/frame-stream.mjs.map +7 -0
- package/dist/modules/agent-client/http.d.mts +68 -0
- package/dist/modules/agent-client/http.mjs +168 -0
- package/dist/modules/agent-client/http.mjs.map +7 -0
- package/dist/modules/agent-client/index.d.mts +17 -0
- package/dist/modules/agent-client/index.mjs +86 -0
- package/dist/modules/agent-client/index.mjs.map +7 -0
- package/dist/modules/agent-client/live-stream.d.mts +18 -0
- package/dist/modules/agent-client/live-stream.mjs +94 -0
- package/dist/modules/agent-client/live-stream.mjs.map +7 -0
- package/dist/modules/agent-client/realtime.d.mts +72 -0
- package/dist/modules/agent-client/realtime.mjs +186 -0
- package/dist/modules/agent-client/realtime.mjs.map +7 -0
- package/dist/modules/agent-client/sandbox-uri.d.mts +19 -0
- package/dist/modules/agent-client/sandbox-uri.mjs +43 -0
- package/dist/modules/agent-client/sandbox-uri.mjs.map +7 -0
- package/dist/modules/agent-client/sessions.d.mts +124 -0
- package/dist/modules/agent-client/sessions.mjs +350 -0
- package/dist/modules/agent-client/sessions.mjs.map +7 -0
- package/dist/modules/agent-client/test-fakes.d.mts +40 -0
- package/dist/modules/agent-client/test-fakes.mjs +94 -0
- package/dist/modules/agent-client/test-fakes.mjs.map +7 -0
- package/dist/modules/authoring/approval.d.mts +1 -0
- package/dist/modules/authoring/approval.mjs +2 -0
- package/dist/modules/authoring/approval.mjs.map +7 -0
- package/dist/modules/authoring/channels.d.mts +1 -0
- package/dist/modules/authoring/channels.mjs +2 -0
- package/dist/modules/authoring/channels.mjs.map +7 -0
- package/dist/modules/authoring/final-channel.d.mts +1 -0
- package/dist/modules/authoring/final-channel.mjs +2 -0
- package/dist/modules/authoring/final-channel.mjs.map +7 -0
- package/dist/modules/authoring/hooks.d.mts +1 -0
- package/dist/modules/authoring/hooks.mjs +2 -0
- package/dist/modules/authoring/hooks.mjs.map +7 -0
- package/dist/modules/authoring/index.d.mts +1 -0
- package/dist/modules/authoring/index.mjs +2 -0
- package/dist/modules/authoring/index.mjs.map +7 -0
- package/dist/modules/authoring/instructions.d.mts +1 -0
- package/dist/modules/authoring/instructions.mjs +2 -0
- package/dist/modules/authoring/instructions.mjs.map +7 -0
- package/dist/modules/authoring/providers.d.mts +2 -0
- package/dist/modules/authoring/providers.mjs +1 -0
- package/dist/modules/authoring/providers.mjs.map +7 -0
- package/dist/modules/authoring/tools.d.mts +1 -0
- package/dist/modules/authoring/tools.mjs +2 -0
- package/dist/modules/authoring/tools.mjs.map +7 -0
- package/dist/modules/cli/build.d.mts +22 -0
- package/dist/modules/cli/build.mjs +77 -0
- package/dist/modules/cli/build.mjs.map +7 -0
- package/dist/modules/cli/channel-socket.d.mts +9 -0
- package/dist/modules/cli/channel-socket.mjs +114 -0
- package/dist/modules/cli/channel-socket.mjs.map +7 -0
- package/dist/modules/cli/cli.d.mts +16 -0
- package/dist/modules/cli/cli.mjs +151 -0
- package/dist/modules/cli/cli.mjs.map +7 -0
- package/dist/modules/cli/conformance.d.mts +36 -0
- package/dist/modules/cli/conformance.mjs +220 -0
- package/dist/modules/cli/conformance.mjs.map +7 -0
- package/dist/modules/cli/dev.d.mts +24 -0
- package/dist/modules/cli/dev.mjs +263 -0
- package/dist/modules/cli/dev.mjs.map +7 -0
- package/dist/modules/cli/files.d.mts +18 -0
- package/dist/modules/cli/files.mjs +66 -0
- package/dist/modules/cli/files.mjs.map +7 -0
- package/dist/modules/cli/harness-descriptions.d.mts +20 -0
- package/dist/modules/cli/harness-descriptions.mjs +70 -0
- package/dist/modules/cli/harness-descriptions.mjs.map +7 -0
- package/dist/modules/cli/main.d.mts +2 -0
- package/dist/modules/cli/main.mjs +20 -0
- package/dist/modules/cli/main.mjs.map +7 -0
- package/dist/modules/cli/output.d.mts +14 -0
- package/dist/modules/cli/output.mjs +94 -0
- package/dist/modules/cli/output.mjs.map +7 -0
- package/dist/modules/cli/publish.d.mts +14 -0
- package/dist/modules/cli/publish.mjs +89 -0
- package/dist/modules/cli/publish.mjs.map +7 -0
- package/dist/modules/compile.d.mts +3 -0
- package/dist/modules/compile.mjs +62 -0
- package/dist/modules/compile.mjs.map +7 -0
- package/dist/modules/harnesses.d.mts +1 -0
- package/dist/modules/harnesses.mjs +2 -0
- package/dist/modules/harnesses.mjs.map +7 -0
- package/dist/modules/index.browser.d.mts +4 -0
- package/dist/modules/index.browser.mjs +8 -0
- package/dist/modules/index.browser.mjs.map +7 -0
- package/dist/modules/index.d.mts +3 -0
- package/dist/modules/index.mjs +6 -0
- package/dist/modules/index.mjs.map +7 -0
- package/dist/modules/models.d.mts +1 -0
- package/dist/modules/models.mjs +2 -0
- package/dist/modules/models.mjs.map +7 -0
- package/dist/modules/public.d.mts +6 -0
- package/dist/modules/public.mjs +68 -0
- package/dist/modules/public.mjs.map +7 -0
- package/dist/modules/sandboxes.d.mts +1 -0
- package/dist/modules/sandboxes.mjs +2 -0
- package/dist/modules/sandboxes.mjs.map +7 -0
- package/examples/custom-harness/README.md +16 -0
- package/examples/custom-harness/agent.ts +154 -0
- package/examples/custom-sandbox/README.md +12 -0
- package/examples/custom-sandbox/agent.ts +14 -0
- package/examples/custom-sandbox/sandbox.ts +22 -0
- package/examples/release-scout/README.md +11 -0
- package/examples/release-scout/agent/agent.ts +22 -0
- package/examples/release-scout/agent/channels/github-signature.ts +27 -0
- package/examples/release-scout/agent/channels/github.ts +38 -0
- package/examples/release-scout/agent/connections/linear.ts +9 -0
- package/examples/release-scout/agent/hooks/audit.ts +24 -0
- package/examples/release-scout/agent/instructions/org-context.ts +26 -0
- package/examples/release-scout/agent/instructions.md +2 -0
- package/examples/release-scout/agent/sandbox.ts +20 -0
- package/examples/release-scout/agent/schedules/daily-digest.md +7 -0
- package/examples/release-scout/agent/skills/triage/SKILL.md +8 -0
- package/examples/release-scout/agent/skills/triage/checklist.md +3 -0
- package/examples/release-scout/agent/tools/lookup_release_notes.ts +24 -0
- package/examples/release-scout/client.ts +123 -0
- package/package.json +126 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AcpFrameDirection,
|
|
3
|
+
acpFrameEventSchema,
|
|
4
|
+
acpPendingEventSchema,
|
|
5
|
+
agentRealtimeFrameSchema,
|
|
6
|
+
AgentRealtimeFrameType,
|
|
7
|
+
AgentRealtimeKind,
|
|
8
|
+
agentRealtimeTopics,
|
|
9
|
+
FIRST_ACP_FRAME_SEQ,
|
|
10
|
+
PendingEventState
|
|
11
|
+
} from "@finalai/agent-protocol/agent-platform";
|
|
12
|
+
import { createAsyncQueue, failOnAbort } from "./async-queue.mjs";
|
|
13
|
+
import {
|
|
14
|
+
ClientErrorCode,
|
|
15
|
+
isClientError,
|
|
16
|
+
isRetryableClientError,
|
|
17
|
+
isTerminalRealtimeError,
|
|
18
|
+
realtimeSubscriptionError
|
|
19
|
+
} from "./errors.mjs";
|
|
20
|
+
const defaultStreamTiming = {
|
|
21
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),
|
|
22
|
+
gapRetryDelaysMs: [100, 250, 500, 1e3, 2e3],
|
|
23
|
+
pollIntervalMs: 1e3,
|
|
24
|
+
conversationPollIntervalMs: 5e3,
|
|
25
|
+
replayPageSize: 500,
|
|
26
|
+
pendingJoinDelaysMs: [250, 1e3]
|
|
27
|
+
};
|
|
28
|
+
const PENDING_REQUEST_METHODS = /* @__PURE__ */ new Set(["session/request_permission", "elicitation/create"]);
|
|
29
|
+
const isPendingRequestFrame = (event) => event.direction === AcpFrameDirection.AgentToClient && event.frame.id != null && event.frame.method != null && PENDING_REQUEST_METHODS.has(event.frame.method);
|
|
30
|
+
var FrameOrigin = /* @__PURE__ */ ((FrameOrigin2) => {
|
|
31
|
+
FrameOrigin2["Replay"] = "replay";
|
|
32
|
+
FrameOrigin2["Live"] = "live";
|
|
33
|
+
return FrameOrigin2;
|
|
34
|
+
})(FrameOrigin || {});
|
|
35
|
+
const streamSessionFrames = (args) => {
|
|
36
|
+
const { timing } = args;
|
|
37
|
+
const ahead = /* @__PURE__ */ new Map();
|
|
38
|
+
let nextSeq = Math.max(FIRST_ACP_FRAME_SEQ, args.fromSeq);
|
|
39
|
+
let filling = false;
|
|
40
|
+
let terminalThroughSeq = null;
|
|
41
|
+
let terminalInitialized = args.terminal == null;
|
|
42
|
+
let realtimeSubscribed = false;
|
|
43
|
+
let fillAgain = false;
|
|
44
|
+
let pollTimer = null;
|
|
45
|
+
let terminalTimer = null;
|
|
46
|
+
const closing = new AbortController();
|
|
47
|
+
const readSignal = AbortSignal.any([
|
|
48
|
+
closing.signal,
|
|
49
|
+
...[args.signal, args.closeSignal].filter((signal) => signal != null)
|
|
50
|
+
]);
|
|
51
|
+
let httpFallback = null;
|
|
52
|
+
let unsubscribe = null;
|
|
53
|
+
let detachAbort = () => {
|
|
54
|
+
};
|
|
55
|
+
let detachClose = () => {
|
|
56
|
+
};
|
|
57
|
+
let output = Promise.resolve();
|
|
58
|
+
const pendingIds = /* @__PURE__ */ new Map();
|
|
59
|
+
const pendingWaiters = /* @__PURE__ */ new Map();
|
|
60
|
+
const gapWaiters = /* @__PURE__ */ new Set();
|
|
61
|
+
const stopFallback = () => {
|
|
62
|
+
if (pollTimer != null) clearInterval(pollTimer);
|
|
63
|
+
pollTimer = null;
|
|
64
|
+
httpFallback?.abort();
|
|
65
|
+
httpFallback = null;
|
|
66
|
+
};
|
|
67
|
+
const queue = createAsyncQueue(() => {
|
|
68
|
+
closing.abort();
|
|
69
|
+
if (terminalTimer != null) clearInterval(terminalTimer);
|
|
70
|
+
ahead.clear();
|
|
71
|
+
for (const wake of gapWaiters) wake();
|
|
72
|
+
stopFallback();
|
|
73
|
+
unsubscribe?.();
|
|
74
|
+
detachAbort();
|
|
75
|
+
detachClose();
|
|
76
|
+
for (const waiters of pendingWaiters.values()) for (const wake of waiters) wake();
|
|
77
|
+
pendingWaiters.clear();
|
|
78
|
+
});
|
|
79
|
+
const notePending = (seq, pendingId) => {
|
|
80
|
+
pendingIds.set(seq, pendingId);
|
|
81
|
+
for (const wake of pendingWaiters.get(seq) ?? []) wake();
|
|
82
|
+
pendingWaiters.delete(seq);
|
|
83
|
+
};
|
|
84
|
+
const waitForPending = (seq, ms) => new Promise((resolve) => {
|
|
85
|
+
const waiters = pendingWaiters.get(seq) ?? /* @__PURE__ */ new Set();
|
|
86
|
+
const wake = () => {
|
|
87
|
+
waiters.delete(wake);
|
|
88
|
+
resolve();
|
|
89
|
+
};
|
|
90
|
+
waiters.add(wake);
|
|
91
|
+
pendingWaiters.set(seq, waiters);
|
|
92
|
+
void timing.sleep(ms).then(wake);
|
|
93
|
+
});
|
|
94
|
+
const readOpenPending = async () => {
|
|
95
|
+
if (args.pending == null) return;
|
|
96
|
+
try {
|
|
97
|
+
for (const request of await args.pending(readSignal)) {
|
|
98
|
+
if (request.logSeq != null) notePending(request.logSeq, request.id);
|
|
99
|
+
}
|
|
100
|
+
} catch {
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
const joinPendingId = async (seq) => {
|
|
104
|
+
for (const delay of timing.pendingJoinDelaysMs) {
|
|
105
|
+
if (pendingIds.has(seq) || queue.closed()) break;
|
|
106
|
+
await waitForPending(seq, delay);
|
|
107
|
+
if (pendingIds.has(seq) || queue.closed()) break;
|
|
108
|
+
await readOpenPending();
|
|
109
|
+
}
|
|
110
|
+
return pendingIds.get(seq) ?? null;
|
|
111
|
+
};
|
|
112
|
+
const emit = (event, origin) => {
|
|
113
|
+
if (queue.closed()) return;
|
|
114
|
+
nextSeq = event.seq + 1;
|
|
115
|
+
if (event.pendingId != null) notePending(event.seq, event.pendingId);
|
|
116
|
+
const join = event.pendingId == null && origin === "live" /* Live */ && isPendingRequestFrame(event);
|
|
117
|
+
output = output.then(async () => {
|
|
118
|
+
if (queue.closed() || terminalThroughSeq != null && event.seq > terminalThroughSeq)
|
|
119
|
+
return;
|
|
120
|
+
const value = join ? { ...event, pendingId: await joinPendingId(event.seq) } : event;
|
|
121
|
+
if (queue.closed() || terminalThroughSeq != null && event.seq > terminalThroughSeq)
|
|
122
|
+
return;
|
|
123
|
+
queue.push(value);
|
|
124
|
+
if (args.until?.(value) === true) queue.end();
|
|
125
|
+
}).catch((cause) => queue.fail(cause));
|
|
126
|
+
};
|
|
127
|
+
const drainAhead = () => {
|
|
128
|
+
for (const seq of [...ahead.keys()]) if (seq < nextSeq) ahead.delete(seq);
|
|
129
|
+
for (let held = ahead.get(nextSeq); held != null; held = ahead.get(nextSeq)) {
|
|
130
|
+
if (terminalThroughSeq != null && held.event.seq > terminalThroughSeq) {
|
|
131
|
+
ahead.clear();
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
ahead.delete(held.event.seq);
|
|
135
|
+
emit(held.event, held.origin);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
const deliver = (event, origin) => {
|
|
139
|
+
if (terminalThroughSeq != null && event.seq > terminalThroughSeq) return false;
|
|
140
|
+
if (queue.closed() || event.seq < nextSeq) return false;
|
|
141
|
+
if (!terminalInitialized || event.seq > nextSeq) {
|
|
142
|
+
if (!ahead.has(event.seq) && ahead.size >= timing.replayPageSize * 2) {
|
|
143
|
+
queue.fail(
|
|
144
|
+
new Error("Frame replay fell behind; reconnect from the last received sequence")
|
|
145
|
+
);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
ahead.set(event.seq, { event, origin });
|
|
149
|
+
return true;
|
|
150
|
+
}
|
|
151
|
+
emit(event, origin);
|
|
152
|
+
drainAhead();
|
|
153
|
+
return false;
|
|
154
|
+
};
|
|
155
|
+
const refreshTerminal = async () => {
|
|
156
|
+
if (args.terminal == null || queue.closed()) return;
|
|
157
|
+
const terminal = await args.terminal(readSignal);
|
|
158
|
+
terminalInitialized = true;
|
|
159
|
+
if (terminal == null) return;
|
|
160
|
+
terminalThroughSeq = terminal.throughSeq;
|
|
161
|
+
for (const seq of ahead.keys()) if (seq > terminalThroughSeq) ahead.delete(seq);
|
|
162
|
+
if (terminalThroughSeq < nextSeq) await output.then(() => queue.end());
|
|
163
|
+
};
|
|
164
|
+
const replayOnce = async () => {
|
|
165
|
+
let from = nextSeq;
|
|
166
|
+
for (; ; ) {
|
|
167
|
+
const page = await args.replay({
|
|
168
|
+
fromSeq: from,
|
|
169
|
+
limit: timing.replayPageSize,
|
|
170
|
+
signal: readSignal
|
|
171
|
+
});
|
|
172
|
+
const frames = [...page.frames].sort((left, right) => left.seq - right.seq);
|
|
173
|
+
for (const event of frames) deliver(event, "replay" /* Replay */);
|
|
174
|
+
const last = frames.at(-1)?.seq;
|
|
175
|
+
if (queue.closed() || last == null) return;
|
|
176
|
+
const next = Math.max(nextSeq, last + 1);
|
|
177
|
+
if (next <= from || !page.hasMore && last >= page.headSeq) return;
|
|
178
|
+
from = next;
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const fill = async () => {
|
|
182
|
+
await replayOnce();
|
|
183
|
+
let attempt = 0;
|
|
184
|
+
while (ahead.size > 0 && !queue.closed()) {
|
|
185
|
+
if (ahead.size === 0 || queue.closed()) return;
|
|
186
|
+
const delay = timing.gapRetryDelaysMs[attempt] ?? timing.pollIntervalMs;
|
|
187
|
+
attempt += 1;
|
|
188
|
+
await new Promise((resolve) => {
|
|
189
|
+
const wake = () => {
|
|
190
|
+
clearTimeout(timer);
|
|
191
|
+
gapWaiters.delete(wake);
|
|
192
|
+
resolve();
|
|
193
|
+
};
|
|
194
|
+
const timer = setTimeout(wake, Math.max(1, delay));
|
|
195
|
+
gapWaiters.add(wake);
|
|
196
|
+
});
|
|
197
|
+
if (queue.closed()) return;
|
|
198
|
+
await refreshTerminal();
|
|
199
|
+
if (queue.closed()) return;
|
|
200
|
+
await replayOnce();
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
const requestFill = () => {
|
|
204
|
+
if (filling) {
|
|
205
|
+
fillAgain = true;
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
filling = true;
|
|
209
|
+
void (async () => {
|
|
210
|
+
try {
|
|
211
|
+
do {
|
|
212
|
+
fillAgain = false;
|
|
213
|
+
await refreshTerminal();
|
|
214
|
+
if (queue.closed()) return;
|
|
215
|
+
await fill();
|
|
216
|
+
await refreshTerminal();
|
|
217
|
+
} while (fillAgain && !queue.closed());
|
|
218
|
+
if (realtimeSubscribed) stopFallback();
|
|
219
|
+
if (!args.follow) await output.then(() => queue.end());
|
|
220
|
+
} catch (cause) {
|
|
221
|
+
if (args.follow && isRetryableClientError(cause)) {
|
|
222
|
+
startHttpFallback();
|
|
223
|
+
} else queue.fail(cause);
|
|
224
|
+
} finally {
|
|
225
|
+
filling = false;
|
|
226
|
+
}
|
|
227
|
+
})();
|
|
228
|
+
};
|
|
229
|
+
const pollReplay = () => {
|
|
230
|
+
pollTimer ??= setInterval(requestFill, timing.pollIntervalMs);
|
|
231
|
+
};
|
|
232
|
+
const startHttpFallback = () => {
|
|
233
|
+
if (httpFallback != null || pollTimer != null || queue.closed()) return;
|
|
234
|
+
const open = args.httpStream;
|
|
235
|
+
if (open == null) {
|
|
236
|
+
pollReplay();
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
const controller = new AbortController();
|
|
240
|
+
httpFallback = controller;
|
|
241
|
+
void (async () => {
|
|
242
|
+
while (!controller.signal.aborted && !queue.closed()) {
|
|
243
|
+
try {
|
|
244
|
+
for await (const event of open({ fromSeq: nextSeq, signal: controller.signal })) {
|
|
245
|
+
if (deliver(event, "live" /* Live */)) requestFill();
|
|
246
|
+
}
|
|
247
|
+
} catch (cause) {
|
|
248
|
+
if (controller.signal.aborted || queue.closed()) return;
|
|
249
|
+
if (isClientError(cause) && (cause.code === ClientErrorCode.StreamUnsupported || cause.status === 404 || cause.status === 405)) {
|
|
250
|
+
if (httpFallback === controller) httpFallback = null;
|
|
251
|
+
pollReplay();
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
if (!isRetryableClientError(cause)) {
|
|
255
|
+
queue.fail(cause);
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (controller.signal.aborted) return;
|
|
260
|
+
await timing.sleep(timing.pollIntervalMs);
|
|
261
|
+
}
|
|
262
|
+
})();
|
|
263
|
+
};
|
|
264
|
+
const onFrame = (raw) => {
|
|
265
|
+
for (const wake of gapWaiters) wake();
|
|
266
|
+
const frame = agentRealtimeFrameSchema.safeParse(raw);
|
|
267
|
+
if (!frame.success) return;
|
|
268
|
+
switch (frame.data.type) {
|
|
269
|
+
case AgentRealtimeFrameType.Event: {
|
|
270
|
+
if (frame.data.kind === AgentRealtimeKind.AcpPending) {
|
|
271
|
+
const pending = acpPendingEventSchema.safeParse(frame.data.payload);
|
|
272
|
+
if (pending.success && pending.data.state === PendingEventState.Created && pending.data.pending.logSeq != null) {
|
|
273
|
+
notePending(pending.data.pending.logSeq, pending.data.pending.id);
|
|
274
|
+
}
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
if (frame.data.kind === AgentRealtimeKind.TurnEnded && args.terminal != null) {
|
|
278
|
+
terminalInitialized = false;
|
|
279
|
+
requestFill();
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
if (frame.data.kind !== AgentRealtimeKind.AcpFrame) return;
|
|
283
|
+
const event = acpFrameEventSchema.safeParse(frame.data.payload);
|
|
284
|
+
if (event.success && deliver(event.data, "live" /* Live */)) requestFill();
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
case AgentRealtimeFrameType.Subscribed:
|
|
288
|
+
realtimeSubscribed = true;
|
|
289
|
+
stopFallback();
|
|
290
|
+
requestFill();
|
|
291
|
+
return;
|
|
292
|
+
case AgentRealtimeFrameType.ResyncRequired:
|
|
293
|
+
requestFill();
|
|
294
|
+
return;
|
|
295
|
+
case AgentRealtimeFrameType.Error:
|
|
296
|
+
if (isTerminalRealtimeError(frame.data)) {
|
|
297
|
+
queue.fail(
|
|
298
|
+
realtimeSubscriptionError({
|
|
299
|
+
topic: frame.data.topic ?? agentRealtimeTopics.session(args.sessionId),
|
|
300
|
+
code: frame.data.code ?? "unknown",
|
|
301
|
+
message: frame.data.message ?? "Realtime subscription was refused"
|
|
302
|
+
})
|
|
303
|
+
);
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
realtimeSubscribed = false;
|
|
307
|
+
requestFill();
|
|
308
|
+
startHttpFallback();
|
|
309
|
+
return;
|
|
310
|
+
default:
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
detachAbort = failOnAbort(queue, args.signal);
|
|
315
|
+
detachClose = failOnAbort(queue, args.closeSignal);
|
|
316
|
+
if (!queue.closed()) {
|
|
317
|
+
if (args.follow) {
|
|
318
|
+
const stop = args.transport.subscribe(agentRealtimeTopics.session(args.sessionId), onFrame);
|
|
319
|
+
if (queue.closed()) stop();
|
|
320
|
+
else unsubscribe = stop;
|
|
321
|
+
}
|
|
322
|
+
requestFill();
|
|
323
|
+
if (args.terminal != null && args.follow && !queue.closed())
|
|
324
|
+
terminalTimer = setInterval(requestFill, Math.max(1, timing.conversationPollIntervalMs));
|
|
325
|
+
}
|
|
326
|
+
return queue.iterator;
|
|
327
|
+
};
|
|
328
|
+
export {
|
|
329
|
+
defaultStreamTiming,
|
|
330
|
+
isPendingRequestFrame,
|
|
331
|
+
streamSessionFrames
|
|
332
|
+
};
|
|
333
|
+
//# sourceMappingURL=frame-stream.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/frame-stream.ts"],
|
|
4
|
+
"sourcesContent": ["import {\n AcpFrameDirection,\n acpFrameEventSchema,\n acpPendingEventSchema,\n agentRealtimeFrameSchema,\n AgentRealtimeFrameType,\n AgentRealtimeKind,\n agentRealtimeTopics,\n FIRST_ACP_FRAME_SEQ,\n PendingEventState,\n type AcpFrameEventIface,\n type PendingRequestIface,\n type SessionFramesPageIface,\n} from '@finalai/agent-protocol/agent-platform';\nimport { createAsyncQueue, failOnAbort } from \"./async-queue.mjs\";\nimport {\n ClientErrorCode,\n isClientError,\n isRetryableClientError,\n isTerminalRealtimeError,\n realtimeSubscriptionError,\n} from \"./errors.mjs\";\nimport type { RealtimeTransportFrameIface, RealtimeTransportIface } from \"./realtime.mjs\";\n\n// A session's ACP frames in dense seq order: HTTP replay from `fromSeq`, then\n// live frames from the session topic. Live frames that arrive ahead of the\n// next expected seq are held while replay fills the gap; a (re)subscription,\n// a resync or a transport error triggers replay too, so reconnects never drop\n// frames. A missing seq is retained across replay attempts, never skipped.\n//\n// While the realtime transport reports errors, frames come over Streamable\n// HTTP (the service's server-sent frame stream) until it subscribes again;\n// where the fetch cannot stream, replay is polled instead.\n//\n// An agent request frame (permission, elicitation) carries the `pendingId` to\n// answer it with. Replay pages set it; a live frame is published before its\n// pending request exists, so it is held until the session's `acp.pending`\n// event for its seq arrives, or the open requests are read.\n\nexport interface StreamTimingIface {\n sleep: (ms: number) => Promise<void>;\n /** Delays between replay attempts while a gap stays open. */\n gapRetryDelaysMs: number[];\n /** Replay polling, and Streamable HTTP reconnects, while the realtime transport reports errors. */\n pollIntervalMs: number;\n /** Conversation recovery polling (`events()`) while the realtime transport reports errors. */\n conversationPollIntervalMs: number;\n replayPageSize: number;\n /**\n * How long a live request frame waits for its `acp.pending` event before the\n * open requests are read, once per entry; then it is yielded without one.\n */\n pendingJoinDelaysMs: number[];\n}\n\nexport const defaultStreamTiming: StreamTimingIface = {\n sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms)),\n gapRetryDelaysMs: [100, 250, 500, 1000, 2000],\n pollIntervalMs: 1000,\n conversationPollIntervalMs: 5000,\n replayPageSize: 500,\n pendingJoinDelaysMs: [250, 1000],\n};\n\nexport interface FrameReplayArgsIface {\n fromSeq: number;\n limit: number;\n signal?: AbortSignal;\n}\n\nexport interface SessionFrameStreamArgsIface {\n sessionId: string;\n fromSeq: number;\n /** Keep streaming live frames after replay catches up. */\n follow: boolean;\n replay: (args: FrameReplayArgsIface) => Promise<SessionFramesPageIface>;\n transport: RealtimeTransportIface;\n timing: StreamTimingIface;\n /** The session's open pending requests, to join live request frames with. */\n pending?: (signal?: AbortSignal) => Promise<PendingRequestIface[]>;\n /** Frames from `fromSeq` over Streamable HTTP, followed live. */\n httpStream?: (args: {\n fromSeq: number;\n signal: AbortSignal;\n }) => AsyncIterable<AcpFrameEventIface>;\n signal?: AbortSignal;\n /** The client's close signal: ends the stream like `signal`. */\n closeSignal?: AbortSignal;\n /** Return true to end the stream after yielding this frame. */\n until?: (event: AcpFrameEventIface) => boolean;\n /** A durable terminal receipt, including turns that ended without an ACP echo. */\n terminal?: (signal: AbortSignal) => Promise<{ throughSeq: number } | null>;\n}\n\nconst PENDING_REQUEST_METHODS = new Set(['session/request_permission', 'elicitation/create']);\n\n/** Whether a frame is an agent request the session holds as a pending request. */\nexport const isPendingRequestFrame = (event: AcpFrameEventIface): boolean =>\n event.direction === AcpFrameDirection.AgentToClient &&\n event.frame.id != null &&\n event.frame.method != null &&\n PENDING_REQUEST_METHODS.has(event.frame.method);\n\n/** Where a frame came from: replay pages carry `pendingId`; live and streamed frames may not yet. */\nenum FrameOrigin {\n Replay = 'replay',\n Live = 'live',\n}\n\nexport const streamSessionFrames = (\n args: SessionFrameStreamArgsIface,\n): AsyncIterableIterator<AcpFrameEventIface> => {\n const { timing } = args;\n const ahead = new Map<number, { event: AcpFrameEventIface; origin: FrameOrigin }>();\n // The service numbers frames from 1 and clamps earlier requests; start where frames exist.\n let nextSeq = Math.max(FIRST_ACP_FRAME_SEQ, args.fromSeq);\n let filling = false;\n let terminalThroughSeq: number | null = null;\n let terminalInitialized = args.terminal == null;\n let realtimeSubscribed = false;\n let fillAgain = false;\n let pollTimer: ReturnType<typeof setInterval> | null = null;\n let terminalTimer: ReturnType<typeof setInterval> | null = null;\n const closing = new AbortController();\n const readSignal = AbortSignal.any([\n closing.signal,\n ...[args.signal, args.closeSignal].filter((signal) => signal != null),\n ]);\n let httpFallback: AbortController | null = null;\n let unsubscribe: (() => void) | null = null;\n let detachAbort: () => void = () => {};\n let detachClose: () => void = () => {};\n // Frames are yielded through one chain, so a request frame waiting for its\n // pending id holds back the frames after it.\n let output: Promise<void> = Promise.resolve();\n const pendingIds = new Map<number, string>();\n const pendingWaiters = new Map<number, Set<() => void>>();\n const gapWaiters = new Set<() => void>();\n\n const stopFallback = (): void => {\n if (pollTimer != null) clearInterval(pollTimer);\n pollTimer = null;\n httpFallback?.abort();\n httpFallback = null;\n };\n\n const queue = createAsyncQueue<AcpFrameEventIface>(() => {\n closing.abort();\n if (terminalTimer != null) clearInterval(terminalTimer);\n ahead.clear();\n for (const wake of gapWaiters) wake();\n stopFallback();\n unsubscribe?.();\n detachAbort();\n detachClose();\n for (const waiters of pendingWaiters.values()) for (const wake of waiters) wake();\n pendingWaiters.clear();\n });\n\n const notePending = (seq: number, pendingId: string): void => {\n pendingIds.set(seq, pendingId);\n for (const wake of pendingWaiters.get(seq) ?? []) wake();\n pendingWaiters.delete(seq);\n };\n\n const waitForPending = (seq: number, ms: number): Promise<void> =>\n new Promise((resolve) => {\n const waiters = pendingWaiters.get(seq) ?? new Set<() => void>();\n const wake = (): void => {\n waiters.delete(wake);\n resolve();\n };\n waiters.add(wake);\n pendingWaiters.set(seq, waiters);\n void timing.sleep(ms).then(wake);\n });\n\n const readOpenPending = async (): Promise<void> => {\n if (args.pending == null) return;\n try {\n for (const request of await args.pending(readSignal)) {\n if (request.logSeq != null) notePending(request.logSeq, request.id);\n }\n } catch {\n // The next attempt, or the frame without an id.\n }\n };\n\n const joinPendingId = async (seq: number): Promise<string | null> => {\n for (const delay of timing.pendingJoinDelaysMs) {\n if (pendingIds.has(seq) || queue.closed()) break;\n await waitForPending(seq, delay);\n if (pendingIds.has(seq) || queue.closed()) break;\n await readOpenPending();\n }\n return pendingIds.get(seq) ?? null;\n };\n\n const emit = (event: AcpFrameEventIface, origin: FrameOrigin): void => {\n if (queue.closed()) return;\n nextSeq = event.seq + 1;\n if (event.pendingId != null) notePending(event.seq, event.pendingId);\n const join =\n event.pendingId == null && origin === FrameOrigin.Live && isPendingRequestFrame(event);\n output = output\n .then(async () => {\n if (queue.closed() || (terminalThroughSeq != null && event.seq > terminalThroughSeq))\n return;\n const value = join ? { ...event, pendingId: await joinPendingId(event.seq) } : event;\n if (queue.closed() || (terminalThroughSeq != null && event.seq > terminalThroughSeq))\n return;\n queue.push(value);\n if (args.until?.(value) === true) queue.end();\n })\n .catch((cause: unknown) => queue.fail(cause));\n };\n\n const drainAhead = (): void => {\n for (const seq of [...ahead.keys()]) if (seq < nextSeq) ahead.delete(seq);\n for (let held = ahead.get(nextSeq); held != null; held = ahead.get(nextSeq)) {\n if (terminalThroughSeq != null && held.event.seq > terminalThroughSeq) {\n ahead.clear();\n return;\n }\n ahead.delete(held.event.seq);\n emit(held.event, held.origin);\n }\n };\n\n const deliver = (event: AcpFrameEventIface, origin: FrameOrigin): boolean => {\n if (terminalThroughSeq != null && event.seq > terminalThroughSeq) return false;\n if (queue.closed() || event.seq < nextSeq) return false;\n if (!terminalInitialized || event.seq > nextSeq) {\n if (!ahead.has(event.seq) && ahead.size >= timing.replayPageSize * 2) {\n queue.fail(\n new Error('Frame replay fell behind; reconnect from the last received sequence'),\n );\n return false;\n }\n ahead.set(event.seq, { event, origin });\n return true;\n }\n emit(event, origin);\n drainAhead();\n return false;\n };\n\n const refreshTerminal = async (): Promise<void> => {\n if (args.terminal == null || queue.closed()) return;\n const terminal = await args.terminal(readSignal);\n terminalInitialized = true;\n if (terminal == null) return;\n terminalThroughSeq = terminal.throughSeq;\n for (const seq of ahead.keys()) if (seq > terminalThroughSeq) ahead.delete(seq);\n if (terminalThroughSeq < nextSeq) await output.then(() => queue.end());\n };\n\n /** Pages forward from the next expected seq until the page reaches the log head. */\n const replayOnce = async (): Promise<void> => {\n let from = nextSeq;\n for (;;) {\n const page = await args.replay({\n fromSeq: from,\n limit: timing.replayPageSize,\n signal: readSignal,\n });\n const frames = [...page.frames].sort((left, right) => left.seq - right.seq);\n for (const event of frames) deliver(event, FrameOrigin.Replay);\n const last = frames.at(-1)?.seq;\n if (queue.closed() || last == null) return;\n // Page by what the server returned, not by what was emitted, so frames held behind a gap still advance it.\n const next = Math.max(nextSeq, last + 1);\n if (next <= from || (!page.hasMore && last >= page.headSeq)) return;\n from = next;\n }\n };\n\n const fill = async (): Promise<void> => {\n await replayOnce();\n let attempt = 0;\n while (ahead.size > 0 && !queue.closed()) {\n if (ahead.size === 0 || queue.closed()) return;\n const delay = timing.gapRetryDelaysMs[attempt] ?? timing.pollIntervalMs;\n attempt += 1;\n await new Promise<void>((resolve) => {\n const wake = () => {\n clearTimeout(timer);\n gapWaiters.delete(wake);\n resolve();\n };\n const timer = setTimeout(wake, Math.max(1, delay));\n gapWaiters.add(wake);\n });\n if (queue.closed()) return;\n await refreshTerminal();\n if (queue.closed()) return;\n await replayOnce();\n }\n };\n\n const requestFill = (): void => {\n if (filling) {\n fillAgain = true;\n return;\n }\n filling = true;\n void (async () => {\n try {\n do {\n fillAgain = false;\n await refreshTerminal();\n if (queue.closed()) return;\n await fill();\n await refreshTerminal();\n } while (fillAgain && !queue.closed());\n if (realtimeSubscribed) stopFallback();\n if (!args.follow) await output.then(() => queue.end());\n } catch (cause) {\n if (args.follow && isRetryableClientError(cause)) {\n startHttpFallback();\n } else queue.fail(cause);\n } finally {\n filling = false;\n }\n })();\n };\n\n const pollReplay = (): void => {\n pollTimer ??= setInterval(requestFill, timing.pollIntervalMs);\n };\n\n /** Streams frames over HTTP until the transport subscribes again or the stream ends. */\n const startHttpFallback = (): void => {\n if (httpFallback != null || pollTimer != null || queue.closed()) return;\n const open = args.httpStream;\n if (open == null) {\n pollReplay();\n return;\n }\n const controller = new AbortController();\n httpFallback = controller;\n void (async () => {\n while (!controller.signal.aborted && !queue.closed()) {\n try {\n for await (const event of open({ fromSeq: nextSeq, signal: controller.signal })) {\n if (deliver(event, FrameOrigin.Live)) requestFill();\n }\n } catch (cause) {\n if (controller.signal.aborted || queue.closed()) return;\n if (\n isClientError(cause) &&\n (cause.code === ClientErrorCode.StreamUnsupported ||\n cause.status === 404 ||\n cause.status === 405)\n ) {\n if (httpFallback === controller) httpFallback = null;\n pollReplay();\n return;\n }\n if (!isRetryableClientError(cause)) {\n queue.fail(cause);\n return;\n }\n }\n if (controller.signal.aborted) return;\n await timing.sleep(timing.pollIntervalMs);\n }\n })();\n };\n\n const onFrame = (raw: RealtimeTransportFrameIface): void => {\n for (const wake of gapWaiters) wake();\n const frame = agentRealtimeFrameSchema.safeParse(raw);\n if (!frame.success) return;\n switch (frame.data.type) {\n case AgentRealtimeFrameType.Event: {\n if (frame.data.kind === AgentRealtimeKind.AcpPending) {\n const pending = acpPendingEventSchema.safeParse(frame.data.payload);\n if (\n pending.success &&\n pending.data.state === PendingEventState.Created &&\n pending.data.pending.logSeq != null\n ) {\n notePending(pending.data.pending.logSeq, pending.data.pending.id);\n }\n return;\n }\n if (frame.data.kind === AgentRealtimeKind.TurnEnded && args.terminal != null) {\n terminalInitialized = false;\n requestFill();\n return;\n }\n if (frame.data.kind !== AgentRealtimeKind.AcpFrame) return;\n const event = acpFrameEventSchema.safeParse(frame.data.payload);\n if (event.success && deliver(event.data, FrameOrigin.Live)) requestFill();\n return;\n }\n case AgentRealtimeFrameType.Subscribed:\n realtimeSubscribed = true;\n stopFallback();\n requestFill();\n return;\n case AgentRealtimeFrameType.ResyncRequired:\n requestFill();\n return;\n case AgentRealtimeFrameType.Error:\n if (isTerminalRealtimeError(frame.data)) {\n queue.fail(\n realtimeSubscriptionError({\n topic: frame.data.topic ?? agentRealtimeTopics.session(args.sessionId),\n code: frame.data.code ?? 'unknown',\n message: frame.data.message ?? 'Realtime subscription was refused',\n }),\n );\n return;\n }\n realtimeSubscribed = false;\n // Retryable: the transport resubscribes; Streamable HTTP covers the outage.\n requestFill();\n startHttpFallback();\n return;\n default:\n return;\n }\n };\n\n detachAbort = failOnAbort(queue, args.signal);\n detachClose = failOnAbort(queue, args.closeSignal);\n if (!queue.closed()) {\n // Subscribe before the first replay so nothing published in between is lost.\n if (args.follow) {\n const stop = args.transport.subscribe(agentRealtimeTopics.session(args.sessionId), onFrame);\n if (queue.closed()) stop();\n else unsubscribe = stop;\n }\n requestFill();\n if (args.terminal != null && args.follow && !queue.closed())\n terminalTimer = setInterval(requestFill, Math.max(1, timing.conversationPollIntervalMs));\n }\n return queue.iterator;\n};\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["FrameOrigin"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type * as z from 'zod';
|
|
2
|
+
import { type AgentClientError } from "./errors.mjs";
|
|
3
|
+
export type FetchIface = (input: string, init: RequestInit) => Promise<Response>;
|
|
4
|
+
/** API key (`fk_...`), a bearer source (console access token, service grant), or the browser's console cookie. */
|
|
5
|
+
export type AgentClientAuth = {
|
|
6
|
+
anonymous: true;
|
|
7
|
+
} | {
|
|
8
|
+
apiKey: string;
|
|
9
|
+
} | {
|
|
10
|
+
bearer: () => Promise<string> | string;
|
|
11
|
+
} | {
|
|
12
|
+
cookies: true;
|
|
13
|
+
};
|
|
14
|
+
/** Extra headers per request (client identity, request source). */
|
|
15
|
+
export type HeadersOption = () => Record<string, string> | Promise<Record<string, string>>;
|
|
16
|
+
export declare enum HttpMethod {
|
|
17
|
+
Get = "GET",
|
|
18
|
+
Post = "POST",
|
|
19
|
+
Patch = "PATCH",
|
|
20
|
+
Put = "PUT",
|
|
21
|
+
Delete = "DELETE"
|
|
22
|
+
}
|
|
23
|
+
export type QueryValue = string | number | boolean | undefined;
|
|
24
|
+
export interface HttpBodyIface {
|
|
25
|
+
/** The strict client-to-server schema the body must satisfy. */
|
|
26
|
+
schema: z.ZodType;
|
|
27
|
+
value: unknown;
|
|
28
|
+
}
|
|
29
|
+
export interface HttpRequestArgsIface<Output> {
|
|
30
|
+
method: HttpMethod;
|
|
31
|
+
path: string;
|
|
32
|
+
query?: Record<string, QueryValue>;
|
|
33
|
+
body?: HttpBodyIface;
|
|
34
|
+
response: z.ZodType<Output>;
|
|
35
|
+
signal?: AbortSignal;
|
|
36
|
+
keepalive?: boolean;
|
|
37
|
+
/** Ids the error mapping attaches to PendingRequestError / TurnRejectedError. */
|
|
38
|
+
context?: {
|
|
39
|
+
pendingId?: string;
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface HttpIface {
|
|
44
|
+
request: <Output>(args: HttpRequestArgsIface<Output>) => Promise<Output>;
|
|
45
|
+
/** Auth and caller headers for non-fetch transports (ACP WebSocket). */
|
|
46
|
+
headers: () => Promise<Record<string, string>>;
|
|
47
|
+
url: (path: string) => URL;
|
|
48
|
+
fetch: FetchIface;
|
|
49
|
+
usesCookies: boolean;
|
|
50
|
+
}
|
|
51
|
+
export interface CreateHttpOptionsIface {
|
|
52
|
+
host: string;
|
|
53
|
+
auth: AgentClientAuth;
|
|
54
|
+
headers?: HeadersOption;
|
|
55
|
+
fetch?: FetchIface;
|
|
56
|
+
}
|
|
57
|
+
interface ErrorResponseArgsIface {
|
|
58
|
+
status: number;
|
|
59
|
+
text: string;
|
|
60
|
+
context: HttpRequestArgsIface<unknown>['context'];
|
|
61
|
+
}
|
|
62
|
+
/** Maps a non-2xx response to the SDK error family. */
|
|
63
|
+
export declare const errorFromResponse: (args: ErrorResponseArgsIface) => AgentClientError;
|
|
64
|
+
/** Where the session service mounts its API routes when `host` is a bare origin. */
|
|
65
|
+
export declare const DEFAULT_API_PREFIX = "/api";
|
|
66
|
+
export declare const joinHostPath: (host: string, path: string) => URL;
|
|
67
|
+
export declare const createHttp: (options: CreateHttpOptionsIface) => HttpIface;
|
|
68
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { jsonValueSchema } from "@finalai/agent-protocol";
|
|
2
|
+
import { AgentApiErrorCode, apiErrorResponseSchema } from "@finalai/agent-protocol/agent-platform";
|
|
3
|
+
import {
|
|
4
|
+
clientError,
|
|
5
|
+
ClientErrorCode,
|
|
6
|
+
pendingRequestError,
|
|
7
|
+
PendingRequestErrorReason,
|
|
8
|
+
turnRejectedError
|
|
9
|
+
} from "./errors.mjs";
|
|
10
|
+
var HttpMethod = /* @__PURE__ */ ((HttpMethod2) => {
|
|
11
|
+
HttpMethod2["Get"] = "GET";
|
|
12
|
+
HttpMethod2["Post"] = "POST";
|
|
13
|
+
HttpMethod2["Patch"] = "PATCH";
|
|
14
|
+
HttpMethod2["Put"] = "PUT";
|
|
15
|
+
HttpMethod2["Delete"] = "DELETE";
|
|
16
|
+
return HttpMethod2;
|
|
17
|
+
})(HttpMethod || {});
|
|
18
|
+
const pendingReasonByCode = /* @__PURE__ */ new Map([
|
|
19
|
+
[AgentApiErrorCode.PendingNotFound, PendingRequestErrorReason.NotFound],
|
|
20
|
+
[AgentApiErrorCode.PendingNotInAudience, PendingRequestErrorReason.NotInAudience],
|
|
21
|
+
[AgentApiErrorCode.PendingAlreadyResolved, PendingRequestErrorReason.AlreadyResolved],
|
|
22
|
+
[AgentApiErrorCode.PendingExpired, PendingRequestErrorReason.Expired]
|
|
23
|
+
]);
|
|
24
|
+
const parseJson = (text) => {
|
|
25
|
+
if (text.trim().length === 0) return { ok: true, value: {} };
|
|
26
|
+
try {
|
|
27
|
+
const parsed = jsonValueSchema.safeParse(JSON.parse(text));
|
|
28
|
+
return parsed.success ? { ok: true, value: parsed.data } : { ok: false };
|
|
29
|
+
} catch {
|
|
30
|
+
return { ok: false };
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
const errorFromResponse = (args) => {
|
|
34
|
+
const json = parseJson(args.text);
|
|
35
|
+
const body = json.ok ? json.value : args.text.length > 0 ? args.text : null;
|
|
36
|
+
const parsed = apiErrorResponseSchema.safeParse(body);
|
|
37
|
+
if (!parsed.success) {
|
|
38
|
+
return clientError({
|
|
39
|
+
message: `Request failed with status ${args.status}`,
|
|
40
|
+
status: args.status,
|
|
41
|
+
code: ClientErrorCode.Unknown,
|
|
42
|
+
body
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
const detail = parsed.data.error;
|
|
46
|
+
const message = detail.message.length > 0 ? detail.message : detail.code;
|
|
47
|
+
if (detail.code === AgentApiErrorCode.TurnRejected) {
|
|
48
|
+
return turnRejectedError({
|
|
49
|
+
message,
|
|
50
|
+
status: args.status,
|
|
51
|
+
code: detail.code,
|
|
52
|
+
body,
|
|
53
|
+
sessionId: detail.sessionId ?? args.context?.sessionId ?? null,
|
|
54
|
+
state: detail.state
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
const reason = pendingReasonByCode.get(detail.code);
|
|
58
|
+
if (reason != null) {
|
|
59
|
+
return pendingRequestError({
|
|
60
|
+
message,
|
|
61
|
+
status: args.status,
|
|
62
|
+
code: detail.code,
|
|
63
|
+
body,
|
|
64
|
+
pendingId: detail.pendingId ?? args.context?.pendingId ?? null,
|
|
65
|
+
reason
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
return clientError({ message, status: args.status, code: detail.code, body });
|
|
69
|
+
};
|
|
70
|
+
const resolveHeaders = async (option) => option == null ? {} : await option();
|
|
71
|
+
const authHeaders = async (auth) => {
|
|
72
|
+
if ("apiKey" in auth) return { authorization: `Bearer ${auth.apiKey}` };
|
|
73
|
+
if ("bearer" in auth) return { authorization: `Bearer ${await auth.bearer()}` };
|
|
74
|
+
return {};
|
|
75
|
+
};
|
|
76
|
+
const isAbort = (signal) => signal?.aborted === true;
|
|
77
|
+
const DEFAULT_API_PREFIX = "/api";
|
|
78
|
+
const joinHostPath = (host, path) => {
|
|
79
|
+
const base = new URL(host);
|
|
80
|
+
const trimmed = base.pathname.replace(/\/+$/, "");
|
|
81
|
+
const prefix = trimmed.length === 0 ? DEFAULT_API_PREFIX : trimmed;
|
|
82
|
+
const [pathname, search] = path.split("?", 2);
|
|
83
|
+
const target = new URL(base.origin);
|
|
84
|
+
target.pathname = `${prefix}${pathname?.startsWith("/") ? "" : "/"}${pathname ?? ""}`;
|
|
85
|
+
if (search != null) target.search = search;
|
|
86
|
+
return target;
|
|
87
|
+
};
|
|
88
|
+
const createHttp = (options) => {
|
|
89
|
+
const fetchImpl = options.fetch ?? ((input, init) => globalThis.fetch(input, init));
|
|
90
|
+
const usesCookies = "cookies" in options.auth;
|
|
91
|
+
const headers = async () => ({
|
|
92
|
+
...await resolveHeaders(options.headers),
|
|
93
|
+
...await authHeaders(options.auth)
|
|
94
|
+
});
|
|
95
|
+
const url = (path) => joinHostPath(options.host, path);
|
|
96
|
+
const request = async (args) => {
|
|
97
|
+
const target = url(args.path);
|
|
98
|
+
for (const [key, value] of Object.entries(args.query ?? {})) {
|
|
99
|
+
if (value !== void 0) target.searchParams.set(key, String(value));
|
|
100
|
+
}
|
|
101
|
+
let body;
|
|
102
|
+
if (args.body != null) {
|
|
103
|
+
const validated = args.body.schema.safeParse(args.body.value);
|
|
104
|
+
if (!validated.success) {
|
|
105
|
+
throw clientError({
|
|
106
|
+
message: `Invalid request for ${args.method} ${args.path}: ${validated.error.message}`,
|
|
107
|
+
status: 0,
|
|
108
|
+
code: ClientErrorCode.InvalidRequest
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
body = JSON.stringify(validated.data);
|
|
112
|
+
}
|
|
113
|
+
const baseHeaders = { accept: "application/json", ...await headers() };
|
|
114
|
+
const requestHeaders = body === void 0 ? baseHeaders : { ...baseHeaders, "content-type": "application/json" };
|
|
115
|
+
let response;
|
|
116
|
+
let text;
|
|
117
|
+
try {
|
|
118
|
+
response = await fetchImpl(target.toString(), {
|
|
119
|
+
method: args.method,
|
|
120
|
+
headers: requestHeaders,
|
|
121
|
+
body,
|
|
122
|
+
signal: args.signal,
|
|
123
|
+
credentials: "anonymous" in options.auth ? "omit" : usesCookies ? "include" : void 0,
|
|
124
|
+
keepalive: args.keepalive
|
|
125
|
+
});
|
|
126
|
+
text = await response.text();
|
|
127
|
+
} catch (cause) {
|
|
128
|
+
if (isAbort(args.signal)) throw cause;
|
|
129
|
+
throw clientError({
|
|
130
|
+
message: `Network request to ${target.pathname} failed`,
|
|
131
|
+
status: 0,
|
|
132
|
+
code: ClientErrorCode.Network,
|
|
133
|
+
cause
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
throw errorFromResponse({ status: response.status, text, context: args.context });
|
|
138
|
+
}
|
|
139
|
+
const json = parseJson(text);
|
|
140
|
+
if (!json.ok) {
|
|
141
|
+
throw clientError({
|
|
142
|
+
message: `Response from ${args.method} ${args.path} was not JSON`,
|
|
143
|
+
status: response.status,
|
|
144
|
+
code: ClientErrorCode.InvalidResponse,
|
|
145
|
+
body: text
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const parsed = args.response.safeParse(json.value);
|
|
149
|
+
if (!parsed.success) {
|
|
150
|
+
throw clientError({
|
|
151
|
+
message: `Unexpected response from ${args.method} ${args.path}: ${parsed.error.message}`,
|
|
152
|
+
status: response.status,
|
|
153
|
+
code: ClientErrorCode.InvalidResponse,
|
|
154
|
+
body: json.value
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
return parsed.data;
|
|
158
|
+
};
|
|
159
|
+
return { request, headers, url, fetch: fetchImpl, usesCookies };
|
|
160
|
+
};
|
|
161
|
+
export {
|
|
162
|
+
DEFAULT_API_PREFIX,
|
|
163
|
+
HttpMethod,
|
|
164
|
+
createHttp,
|
|
165
|
+
errorFromResponse,
|
|
166
|
+
joinHostPath
|
|
167
|
+
};
|
|
168
|
+
//# sourceMappingURL=http.mjs.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../../../../src/agent-client/http.ts"],
|
|
4
|
+
"sourcesContent": ["import { jsonValueSchema, type JsonValue } from '@finalai/agent-protocol';\nimport { AgentApiErrorCode, apiErrorResponseSchema } from '@finalai/agent-protocol/agent-platform';\nimport type * as z from 'zod';\nimport {\n clientError,\n ClientErrorCode,\n pendingRequestError,\n PendingRequestErrorReason,\n turnRejectedError,\n type AgentClientError,\n} from \"./errors.mjs\";\n\nexport type FetchIface = (input: string, init: RequestInit) => Promise<Response>;\n\n/** API key (`fk_...`), a bearer source (console access token, service grant), or the browser's console cookie. */\nexport type AgentClientAuth =\n | { anonymous: true }\n | { apiKey: string }\n | { bearer: () => Promise<string> | string }\n | { cookies: true };\n\n/** Extra headers per request (client identity, request source). */\nexport type HeadersOption = () => Record<string, string> | Promise<Record<string, string>>;\n\nexport enum HttpMethod {\n Get = 'GET',\n Post = 'POST',\n Patch = 'PATCH',\n Put = 'PUT',\n Delete = 'DELETE',\n}\n\nexport type QueryValue = string | number | boolean | undefined;\n\nexport interface HttpBodyIface {\n /** The strict client-to-server schema the body must satisfy. */\n schema: z.ZodType;\n value: unknown;\n}\n\nexport interface HttpRequestArgsIface<Output> {\n method: HttpMethod;\n path: string;\n query?: Record<string, QueryValue>;\n body?: HttpBodyIface;\n response: z.ZodType<Output>;\n signal?: AbortSignal;\n keepalive?: boolean;\n /** Ids the error mapping attaches to PendingRequestError / TurnRejectedError. */\n context?: { pendingId?: string; sessionId?: string };\n}\n\nexport interface HttpIface {\n request: <Output>(args: HttpRequestArgsIface<Output>) => Promise<Output>;\n /** Auth and caller headers for non-fetch transports (ACP WebSocket). */\n headers: () => Promise<Record<string, string>>;\n url: (path: string) => URL;\n fetch: FetchIface;\n usesCookies: boolean;\n}\n\nexport interface CreateHttpOptionsIface {\n host: string;\n auth: AgentClientAuth;\n headers?: HeadersOption;\n fetch?: FetchIface;\n}\n\nconst pendingReasonByCode = new Map<string, PendingRequestErrorReason>([\n [AgentApiErrorCode.PendingNotFound, PendingRequestErrorReason.NotFound],\n [AgentApiErrorCode.PendingNotInAudience, PendingRequestErrorReason.NotInAudience],\n [AgentApiErrorCode.PendingAlreadyResolved, PendingRequestErrorReason.AlreadyResolved],\n [AgentApiErrorCode.PendingExpired, PendingRequestErrorReason.Expired],\n]);\n\nconst parseJson = (text: string): { ok: true; value: JsonValue } | { ok: false } => {\n if (text.trim().length === 0) return { ok: true, value: {} };\n try {\n const parsed = jsonValueSchema.safeParse(JSON.parse(text));\n return parsed.success ? { ok: true, value: parsed.data } : { ok: false };\n } catch {\n return { ok: false };\n }\n};\n\ninterface ErrorResponseArgsIface {\n status: number;\n text: string;\n context: HttpRequestArgsIface<unknown>['context'];\n}\n\n/** Maps a non-2xx response to the SDK error family. */\nexport const errorFromResponse = (args: ErrorResponseArgsIface): AgentClientError => {\n const json = parseJson(args.text);\n const body = json.ok ? json.value : args.text.length > 0 ? args.text : null;\n const parsed = apiErrorResponseSchema.safeParse(body);\n if (!parsed.success) {\n return clientError({\n message: `Request failed with status ${args.status}`,\n status: args.status,\n code: ClientErrorCode.Unknown,\n body,\n });\n }\n const detail = parsed.data.error;\n const message = detail.message.length > 0 ? detail.message : detail.code;\n if (detail.code === AgentApiErrorCode.TurnRejected) {\n return turnRejectedError({\n message,\n status: args.status,\n code: detail.code,\n body,\n sessionId: detail.sessionId ?? args.context?.sessionId ?? null,\n state: detail.state,\n });\n }\n const reason = pendingReasonByCode.get(detail.code);\n if (reason != null) {\n return pendingRequestError({\n message,\n status: args.status,\n code: detail.code,\n body,\n pendingId: detail.pendingId ?? args.context?.pendingId ?? null,\n reason,\n });\n }\n return clientError({ message, status: args.status, code: detail.code, body });\n};\n\nconst resolveHeaders = async (option: HeadersOption | undefined): Promise<Record<string, string>> =>\n option == null ? {} : await option();\n\nconst authHeaders = async (auth: AgentClientAuth): Promise<Record<string, string>> => {\n if ('apiKey' in auth) return { authorization: `Bearer ${auth.apiKey}` };\n if ('bearer' in auth) return { authorization: `Bearer ${await auth.bearer()}` };\n return {};\n};\n\nconst isAbort = (signal: AbortSignal | undefined): boolean => signal?.aborted === true;\n\n/** Where the session service mounts its API routes when `host` is a bare origin. */\nexport const DEFAULT_API_PREFIX = '/api';\n\n// `host` is the session service's origin (`https://agent-session.final.ai`),\n// whose routes live under `/api`, or a base URL with its own path prefix\n// (console-server's `/api/agent-platform` proxy, or `<origin>/api`), which\n// `new URL(path, host)` would drop for absolute paths.\nexport const joinHostPath = (host: string, path: string): URL => {\n const base = new URL(host);\n const trimmed = base.pathname.replace(/\\/+$/, '');\n const prefix = trimmed.length === 0 ? DEFAULT_API_PREFIX : trimmed;\n const [pathname, search] = path.split('?', 2);\n const target = new URL(base.origin);\n target.pathname = `${prefix}${pathname?.startsWith('/') ? '' : '/'}${pathname ?? ''}`;\n if (search != null) target.search = search;\n return target;\n};\n\nexport const createHttp = (options: CreateHttpOptionsIface): HttpIface => {\n const fetchImpl: FetchIface = options.fetch ?? ((input, init) => globalThis.fetch(input, init));\n const usesCookies = 'cookies' in options.auth;\n\n const headers = async (): Promise<Record<string, string>> => ({\n ...(await resolveHeaders(options.headers)),\n ...(await authHeaders(options.auth)),\n });\n\n const url = (path: string): URL => joinHostPath(options.host, path);\n\n const request = async <Output>(args: HttpRequestArgsIface<Output>): Promise<Output> => {\n const target = url(args.path);\n for (const [key, value] of Object.entries(args.query ?? {})) {\n if (value !== undefined) target.searchParams.set(key, String(value));\n }\n let body: string | undefined;\n if (args.body != null) {\n const validated = args.body.schema.safeParse(args.body.value);\n if (!validated.success) {\n throw clientError({\n message: `Invalid request for ${args.method} ${args.path}: ${validated.error.message}`,\n status: 0,\n code: ClientErrorCode.InvalidRequest,\n });\n }\n body = JSON.stringify(validated.data);\n }\n const baseHeaders = { accept: 'application/json', ...(await headers()) };\n const requestHeaders =\n body === undefined ? baseHeaders : { ...baseHeaders, 'content-type': 'application/json' };\n\n let response: Response;\n let text: string;\n try {\n response = await fetchImpl(target.toString(), {\n method: args.method,\n headers: requestHeaders,\n body,\n signal: args.signal,\n credentials: 'anonymous' in options.auth ? 'omit' : usesCookies ? 'include' : undefined,\n keepalive: args.keepalive,\n });\n // Read failures are network failures too.\n text = await response.text();\n } catch (cause) {\n if (isAbort(args.signal)) throw cause;\n throw clientError({\n message: `Network request to ${target.pathname} failed`,\n status: 0,\n code: ClientErrorCode.Network,\n cause,\n });\n }\n // Read text first so proxy and CDN error bodies survive non-JSON responses.\n if (!response.ok) {\n throw errorFromResponse({ status: response.status, text, context: args.context });\n }\n const json = parseJson(text);\n if (!json.ok) {\n throw clientError({\n message: `Response from ${args.method} ${args.path} was not JSON`,\n status: response.status,\n code: ClientErrorCode.InvalidResponse,\n body: text,\n });\n }\n const parsed = args.response.safeParse(json.value);\n if (!parsed.success) {\n throw clientError({\n message: `Unexpected response from ${args.method} ${args.path}: ${parsed.error.message}`,\n status: response.status,\n code: ClientErrorCode.InvalidResponse,\n body: json.value,\n });\n }\n return parsed.data;\n };\n\n return { request, headers, url, fetch: fetchImpl, usesCookies };\n};\n"],
|
|
5
|
+
"mappings": "AAAA,SAAS,uBAAuC;AAChD,SAAS,mBAAmB,8BAA8B;AAE1D;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEK;AAcA,IAAK,aAAL,kBAAKA,gBAAL;AACL,EAAAA,YAAA,SAAM;AACN,EAAAA,YAAA,UAAO;AACP,EAAAA,YAAA,WAAQ;AACR,EAAAA,YAAA,SAAM;AACN,EAAAA,YAAA,YAAS;AALC,SAAAA;AAAA,GAAA;AA4CZ,MAAM,sBAAsB,oBAAI,IAAuC;AAAA,EACrE,CAAC,kBAAkB,iBAAiB,0BAA0B,QAAQ;AAAA,EACtE,CAAC,kBAAkB,sBAAsB,0BAA0B,aAAa;AAAA,EAChF,CAAC,kBAAkB,wBAAwB,0BAA0B,eAAe;AAAA,EACpF,CAAC,kBAAkB,gBAAgB,0BAA0B,OAAO;AACtE,CAAC;AAED,MAAM,YAAY,CAAC,SAAiE;AAClF,MAAI,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO,EAAE,IAAI,MAAM,OAAO,CAAC,EAAE;AAC3D,MAAI;AACF,UAAM,SAAS,gBAAgB,UAAU,KAAK,MAAM,IAAI,CAAC;AACzD,WAAO,OAAO,UAAU,EAAE,IAAI,MAAM,OAAO,OAAO,KAAK,IAAI,EAAE,IAAI,MAAM;AAAA,EACzE,QAAQ;AACN,WAAO,EAAE,IAAI,MAAM;AAAA,EACrB;AACF;AASO,MAAM,oBAAoB,CAAC,SAAmD;AACnF,QAAM,OAAO,UAAU,KAAK,IAAI;AAChC,QAAM,OAAO,KAAK,KAAK,KAAK,QAAQ,KAAK,KAAK,SAAS,IAAI,KAAK,OAAO;AACvE,QAAM,SAAS,uBAAuB,UAAU,IAAI;AACpD,MAAI,CAAC,OAAO,SAAS;AACnB,WAAO,YAAY;AAAA,MACjB,SAAS,8BAA8B,KAAK,MAAM;AAAA,MAClD,QAAQ,KAAK;AAAA,MACb,MAAM,gBAAgB;AAAA,MACtB;AAAA,IACF,CAAC;AAAA,EACH;AACA,QAAM,SAAS,OAAO,KAAK;AAC3B,QAAM,UAAU,OAAO,QAAQ,SAAS,IAAI,OAAO,UAAU,OAAO;AACpE,MAAI,OAAO,SAAS,kBAAkB,cAAc;AAClD,WAAO,kBAAkB;AAAA,MACvB;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,MAAM,OAAO;AAAA,MACb;AAAA,MACA,WAAW,OAAO,aAAa,KAAK,SAAS,aAAa;AAAA,MAC1D,OAAO,OAAO;AAAA,IAChB,CAAC;AAAA,EACH;AACA,QAAM,SAAS,oBAAoB,IAAI,OAAO,IAAI;AAClD,MAAI,UAAU,MAAM;AAClB,WAAO,oBAAoB;AAAA,MACzB;AAAA,MACA,QAAQ,KAAK;AAAA,MACb,MAAM,OAAO;AAAA,MACb;AAAA,MACA,WAAW,OAAO,aAAa,KAAK,SAAS,aAAa;AAAA,MAC1D;AAAA,IACF,CAAC;AAAA,EACH;AACA,SAAO,YAAY,EAAE,SAAS,QAAQ,KAAK,QAAQ,MAAM,OAAO,MAAM,KAAK,CAAC;AAC9E;AAEA,MAAM,iBAAiB,OAAO,WAC5B,UAAU,OAAO,CAAC,IAAI,MAAM,OAAO;AAErC,MAAM,cAAc,OAAO,SAA2D;AACpF,MAAI,YAAY,KAAM,QAAO,EAAE,eAAe,UAAU,KAAK,MAAM,GAAG;AACtE,MAAI,YAAY,KAAM,QAAO,EAAE,eAAe,UAAU,MAAM,KAAK,OAAO,CAAC,GAAG;AAC9E,SAAO,CAAC;AACV;AAEA,MAAM,UAAU,CAAC,WAA6C,QAAQ,YAAY;AAG3E,MAAM,qBAAqB;AAM3B,MAAM,eAAe,CAAC,MAAc,SAAsB;AAC/D,QAAM,OAAO,IAAI,IAAI,IAAI;AACzB,QAAM,UAAU,KAAK,SAAS,QAAQ,QAAQ,EAAE;AAChD,QAAM,SAAS,QAAQ,WAAW,IAAI,qBAAqB;AAC3D,QAAM,CAAC,UAAU,MAAM,IAAI,KAAK,MAAM,KAAK,CAAC;AAC5C,QAAM,SAAS,IAAI,IAAI,KAAK,MAAM;AAClC,SAAO,WAAW,GAAG,MAAM,GAAG,UAAU,WAAW,GAAG,IAAI,KAAK,GAAG,GAAG,YAAY,EAAE;AACnF,MAAI,UAAU,KAAM,QAAO,SAAS;AACpC,SAAO;AACT;AAEO,MAAM,aAAa,CAAC,YAA+C;AACxE,QAAM,YAAwB,QAAQ,UAAU,CAAC,OAAO,SAAS,WAAW,MAAM,OAAO,IAAI;AAC7F,QAAM,cAAc,aAAa,QAAQ;AAEzC,QAAM,UAAU,aAA8C;AAAA,IAC5D,GAAI,MAAM,eAAe,QAAQ,OAAO;AAAA,IACxC,GAAI,MAAM,YAAY,QAAQ,IAAI;AAAA,EACpC;AAEA,QAAM,MAAM,CAAC,SAAsB,aAAa,QAAQ,MAAM,IAAI;AAElE,QAAM,UAAU,OAAe,SAAwD;AACrF,UAAM,SAAS,IAAI,KAAK,IAAI;AAC5B,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,KAAK,SAAS,CAAC,CAAC,GAAG;AAC3D,UAAI,UAAU,OAAW,QAAO,aAAa,IAAI,KAAK,OAAO,KAAK,CAAC;AAAA,IACrE;AACA,QAAI;AACJ,QAAI,KAAK,QAAQ,MAAM;AACrB,YAAM,YAAY,KAAK,KAAK,OAAO,UAAU,KAAK,KAAK,KAAK;AAC5D,UAAI,CAAC,UAAU,SAAS;AACtB,cAAM,YAAY;AAAA,UAChB,SAAS,uBAAuB,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,UAAU,MAAM,OAAO;AAAA,UACpF,QAAQ;AAAA,UACR,MAAM,gBAAgB;AAAA,QACxB,CAAC;AAAA,MACH;AACA,aAAO,KAAK,UAAU,UAAU,IAAI;AAAA,IACtC;AACA,UAAM,cAAc,EAAE,QAAQ,oBAAoB,GAAI,MAAM,QAAQ,EAAG;AACvE,UAAM,iBACJ,SAAS,SAAY,cAAc,EAAE,GAAG,aAAa,gBAAgB,mBAAmB;AAE1F,QAAI;AACJ,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,UAAU,OAAO,SAAS,GAAG;AAAA,QAC5C,QAAQ,KAAK;AAAA,QACb,SAAS;AAAA,QACT;AAAA,QACA,QAAQ,KAAK;AAAA,QACb,aAAa,eAAe,QAAQ,OAAO,SAAS,cAAc,YAAY;AAAA,QAC9E,WAAW,KAAK;AAAA,MAClB,CAAC;AAED,aAAO,MAAM,SAAS,KAAK;AAAA,IAC7B,SAAS,OAAO;AACd,UAAI,QAAQ,KAAK,MAAM,EAAG,OAAM;AAChC,YAAM,YAAY;AAAA,QAChB,SAAS,sBAAsB,OAAO,QAAQ;AAAA,QAC9C,QAAQ;AAAA,QACR,MAAM,gBAAgB;AAAA,QACtB;AAAA,MACF,CAAC;AAAA,IACH;AAEA,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,kBAAkB,EAAE,QAAQ,SAAS,QAAQ,MAAM,SAAS,KAAK,QAAQ,CAAC;AAAA,IAClF;AACA,UAAM,OAAO,UAAU,IAAI;AAC3B,QAAI,CAAC,KAAK,IAAI;AACZ,YAAM,YAAY;AAAA,QAChB,SAAS,iBAAiB,KAAK,MAAM,IAAI,KAAK,IAAI;AAAA,QAClD,QAAQ,SAAS;AAAA,QACjB,MAAM,gBAAgB;AAAA,QACtB,MAAM;AAAA,MACR,CAAC;AAAA,IACH;AACA,UAAM,SAAS,KAAK,SAAS,UAAU,KAAK,KAAK;AACjD,QAAI,CAAC,OAAO,SAAS;AACnB,YAAM,YAAY;AAAA,QAChB,SAAS,4BAA4B,KAAK,MAAM,IAAI,KAAK,IAAI,KAAK,OAAO,MAAM,OAAO;AAAA,QACtF,QAAQ,SAAS;AAAA,QACjB,MAAM,gBAAgB;AAAA,QACtB,MAAM,KAAK;AAAA,MACb,CAAC;AAAA,IACH;AACA,WAAO,OAAO;AAAA,EAChB;AAEA,SAAO,EAAE,SAAS,SAAS,KAAK,OAAO,WAAW,YAAY;AAChE;",
|
|
6
|
+
"names": ["HttpMethod"]
|
|
7
|
+
}
|