@zvada/agent-server 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +317 -0
- package/README.md +34 -4
- package/docs/consuming.md +269 -0
- package/docs/deploy.md +80 -0
- package/docs/harnesses.md +64 -0
- package/docs/rfds/0001-deterministic-echo-ids.md +44 -0
- package/package.json +23 -3
- package/src/client/client.ts +153 -49
- package/src/core/agents/acp/acp-agent.ts +9 -0
- package/src/core/agents/acp/mappings.ts +3 -3
- package/src/core/agents/base.ts +35 -5
- package/src/core/agents/claude-code/adapter.ts +116 -26
- package/src/core/agents/claude-code/claude-agent.ts +44 -7
- package/src/core/agents/claude-code/generator-session.ts +53 -14
- package/src/core/agents/claude-code/options.ts +13 -3
- package/src/core/agents/claude-code/session-manager.ts +9 -4
- package/src/core/agents/codex-app-server/codex-app-server-agent.ts +25 -6
- package/src/core/agents/codex-sdk/codex-sdk-agent.ts +3 -3
- package/src/core/agents/types.ts +1 -1
- package/src/core/diagnostics.ts +59 -0
- package/src/core/index.ts +6 -2
- package/src/core/presets.ts +15 -2
- package/src/core/provision/pins.ts +5 -1
- package/src/core/proxy/anthropic-proxy.ts +76 -3
- package/src/core/runtime/agent-runtime.ts +215 -28
- package/src/core/runtime/event-processor.ts +51 -26
- package/src/core/utils/errors.ts +37 -3
- package/src/protocol/config.ts +8 -6
- package/src/{core/agents/error-classifier.ts → protocol/errors.ts} +33 -7
- package/src/protocol/factories.ts +106 -10
- package/src/protocol/guards.ts +53 -0
- package/src/protocol/index.ts +10 -0
- package/src/protocol/lifecycle.ts +289 -112
- package/src/protocol/meta.ts +14 -0
- package/src/protocol/part-input.ts +56 -7
- package/src/protocol/parts.ts +125 -10
- package/src/protocol/reduce.ts +749 -0
- package/src/protocol/selectors.ts +162 -0
- package/src/protocol/seq-cursor.ts +87 -0
- package/src/protocol/stop-reasons.ts +45 -0
- package/src/protocol/time.ts +23 -0
- package/src/protocol/tokens.ts +23 -0
- package/src/protocol/tool-state.ts +85 -25
- package/src/protocol/verify.ts +440 -0
- package/src/protocol/vocabulary.ts +18 -0
- package/src/protocol/wire.ts +103 -7
- package/src/server/acp/binding.ts +23 -2
- package/src/server/acp/translate.ts +51 -14
- package/src/server/agent-server.ts +109 -6
|
@@ -5,7 +5,12 @@ import type {
|
|
|
5
5
|
PermissionToolCall,
|
|
6
6
|
} from "../../../protocol/index.ts";
|
|
7
7
|
import { AsyncQueue, codexReasoningEffort } from "../../../protocol/index.ts";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
AgentExecuteOptions,
|
|
10
|
+
CancelResult,
|
|
11
|
+
PermissionRequestHandler,
|
|
12
|
+
RawAgentEvent,
|
|
13
|
+
} from "../base.ts";
|
|
9
14
|
import { BaseAgent } from "../base.ts";
|
|
10
15
|
import { configFingerprint } from "../config-fingerprint.ts";
|
|
11
16
|
import { SessionStore } from "../session-store.ts";
|
|
@@ -59,10 +64,10 @@ type UserInput = { type: "text"; text: string; text_elements: [] } | { type: "im
|
|
|
59
64
|
|
|
60
65
|
function mapSandbox(mode: PermissionMode | undefined): string {
|
|
61
66
|
switch (mode) {
|
|
62
|
-
case "
|
|
67
|
+
case "bypass_permissions":
|
|
63
68
|
return "danger-full-access";
|
|
64
|
-
case "
|
|
65
|
-
case "
|
|
69
|
+
case "accept_edits":
|
|
70
|
+
case "dont_ask":
|
|
66
71
|
// Never-ask posture, but inside the normal sandbox — danger-full is
|
|
67
72
|
// reserved for the explicitly dangerous mode.
|
|
68
73
|
return "workspace-write";
|
|
@@ -77,6 +82,14 @@ function toUserInput(input: AgentInput): UserInput[] {
|
|
|
77
82
|
for (const part of input) {
|
|
78
83
|
if (part.type === "text") out.push({ type: "text", text: part.text, text_elements: [] });
|
|
79
84
|
else if (part.url) out.push({ type: "image", url: part.url });
|
|
85
|
+
else if (part.type === "image" && part.data) {
|
|
86
|
+
// Codex's user input carries images by URL only; a pasted image arrives
|
|
87
|
+
// as base64 `data` and used to be dropped SILENTLY while the negotiated
|
|
88
|
+
// capability said supported. A data URL delivers it on the same field —
|
|
89
|
+
// and a Codex build that rejects it fails the turn loudly instead of
|
|
90
|
+
// the prompt losing its attachment with no trace.
|
|
91
|
+
out.push({ type: "image", url: `data:${part.mimeType};base64,${part.data}` });
|
|
92
|
+
}
|
|
80
93
|
}
|
|
81
94
|
return out.length ? out : [{ type: "text", text: "", text_elements: [] }];
|
|
82
95
|
}
|
|
@@ -255,8 +268,14 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
255
268
|
const handler = session.permissionHandler;
|
|
256
269
|
if (!handler) return decline;
|
|
257
270
|
const decision = await handler(approvalToolCall(method, params));
|
|
271
|
+
// Codex's approval reply is a bare verdict — no slot for
|
|
272
|
+
// `decision.updatedInput`, so an edited input cannot be honoured here
|
|
273
|
+
// (only claude-code's `canUseTool` can execute the edit). An edited
|
|
274
|
+
// approval therefore DECLINES: accepting would run input nobody
|
|
275
|
+
// approved.
|
|
258
276
|
switch (decision.decision) {
|
|
259
277
|
case "allow":
|
|
278
|
+
if (decision.updatedInput !== undefined) return decline;
|
|
260
279
|
return v2 ? { decision: "accept" } : { decision: "approved" };
|
|
261
280
|
case "cancel":
|
|
262
281
|
return v2 ? { decision: "cancel" } : { decision: "abort" };
|
|
@@ -369,8 +388,8 @@ export class CodexAppServerAgent extends BaseAgent {
|
|
|
369
388
|
}
|
|
370
389
|
}
|
|
371
390
|
|
|
372
|
-
override async cancel(sessionId: string): Promise<
|
|
373
|
-
await super.cancel(sessionId);
|
|
391
|
+
override async cancel(sessionId: string): Promise<CancelResult> {
|
|
392
|
+
return await super.cancel(sessionId);
|
|
374
393
|
}
|
|
375
394
|
|
|
376
395
|
override async release(sessionId: string): Promise<void> {
|
|
@@ -77,10 +77,10 @@ export function codexThreadCompatible(
|
|
|
77
77
|
|
|
78
78
|
function mapSandbox(mode: PermissionMode | undefined): ThreadOptions["sandboxMode"] {
|
|
79
79
|
switch (mode) {
|
|
80
|
-
case "
|
|
80
|
+
case "bypass_permissions":
|
|
81
81
|
return "danger-full-access";
|
|
82
|
-
case "
|
|
83
|
-
case "
|
|
82
|
+
case "accept_edits":
|
|
83
|
+
case "dont_ask":
|
|
84
84
|
// Never-ask posture, but inside the normal sandbox — danger-full is
|
|
85
85
|
// reserved for the explicitly dangerous mode.
|
|
86
86
|
return "workspace-write";
|
package/src/core/agents/types.ts
CHANGED
|
@@ -26,7 +26,7 @@ export type AdapterEvent =
|
|
|
26
26
|
}
|
|
27
27
|
/** Context-window gauge snapshot (→ `session.usage`). */
|
|
28
28
|
| { kind: "usage"; used: number; size?: number; cost?: number }
|
|
29
|
-
/** History-compaction boundary (→ `session.
|
|
29
|
+
/** History-compaction boundary (→ the `session.compaction` entity). */
|
|
30
30
|
| { kind: "compacted"; trigger?: string; preTokens?: number; postTokens?: number };
|
|
31
31
|
|
|
32
32
|
/** Terminal result of a turn, surfaced on `turn.ended`. */
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The engine's diagnostics port: operational signals the engine used to
|
|
3
|
+
* swallow (best-effort paths that must not fail a turn) surfaced to the host
|
|
4
|
+
* instead. Products log/metric these; the engine never does its own logging.
|
|
5
|
+
* Handler errors are always swallowed — a diagnostics sink must never be able
|
|
6
|
+
* to break a turn.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Known diagnostic kinds (open set — new kinds may appear in minor versions;
|
|
11
|
+
* the `(string & {})` arm keeps the union open without losing autocomplete):
|
|
12
|
+
* - `interruptTimeout` — a cancel's SDK interrupt round-trip timed out
|
|
13
|
+
* (the turn was reported `confirmed: false`; the agent may still run).
|
|
14
|
+
* - `resumeFallback` — a requested resume failed and the turn re-ran on a
|
|
15
|
+
* fresh session (also visible as `session.created.resumed: false`).
|
|
16
|
+
* - `sinkError` — an EventSink emit threw; the event was dropped for that
|
|
17
|
+
* sink and the turn continued.
|
|
18
|
+
* - `proxyUpstreamAuth` — the BYOK proxy's upstream rejected the real key
|
|
19
|
+
* (401/403): the stored key is invalid/expired, not the placeholder.
|
|
20
|
+
*/
|
|
21
|
+
export type DiagnosticKind =
|
|
22
|
+
| "interruptTimeout"
|
|
23
|
+
| "resumeFallback"
|
|
24
|
+
| "sinkError"
|
|
25
|
+
| "proxyUpstreamAuth"
|
|
26
|
+
| (string & {});
|
|
27
|
+
|
|
28
|
+
export interface EngineDiagnostic {
|
|
29
|
+
type: DiagnosticKind;
|
|
30
|
+
sessionId?: string;
|
|
31
|
+
message: string;
|
|
32
|
+
detail?: unknown;
|
|
33
|
+
timestamp: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Declared `=> void` so any callback shape is assignable (TS's void-return
|
|
38
|
+
* exemption covers `() => diagnostics.push(d)` AND async handlers); a
|
|
39
|
+
* returned promise is still detected at runtime and its rejection swallowed.
|
|
40
|
+
*/
|
|
41
|
+
export type DiagnosticHandler = (diagnostic: EngineDiagnostic) => void;
|
|
42
|
+
|
|
43
|
+
/** Invoke a handler without letting it break the calling path — sync throws
|
|
44
|
+
* AND async rejections are swallowed (the diagnostics sink owns its own
|
|
45
|
+
* reliability). Stamps the timestamp so call sites don't repeat it. */
|
|
46
|
+
export function emitDiagnostic(
|
|
47
|
+
handler: DiagnosticHandler | undefined,
|
|
48
|
+
diagnostic: Omit<EngineDiagnostic, "timestamp">,
|
|
49
|
+
): void {
|
|
50
|
+
if (!handler) return;
|
|
51
|
+
try {
|
|
52
|
+
const result = handler({ ...diagnostic, timestamp: Date.now() }) as unknown;
|
|
53
|
+
if (result instanceof Promise) {
|
|
54
|
+
result.catch(() => {});
|
|
55
|
+
}
|
|
56
|
+
} catch {
|
|
57
|
+
// see above
|
|
58
|
+
}
|
|
59
|
+
}
|
package/src/core/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// @zvada/agent-server/core — harness-agnostic agent execution engine.
|
|
2
2
|
|
|
3
3
|
// Runtime
|
|
4
|
-
export { AgentRuntime, type RunSummary } from "./runtime/agent-runtime.ts";
|
|
4
|
+
export { AgentRuntime, type RunSummary, type TurnAdmission } from "./runtime/agent-runtime.ts";
|
|
5
|
+
export { type DiagnosticHandler, type EngineDiagnostic, emitDiagnostic } from "./diagnostics.ts";
|
|
5
6
|
export {
|
|
6
7
|
type EventSink,
|
|
7
8
|
callbackSink,
|
|
@@ -14,6 +15,7 @@ export { EventProcessor } from "./runtime/event-processor.ts";
|
|
|
14
15
|
export {
|
|
15
16
|
type Agent,
|
|
16
17
|
type AgentExecuteOptions,
|
|
18
|
+
type CancelResult,
|
|
17
19
|
type RawAgentEvent,
|
|
18
20
|
BaseAgent,
|
|
19
21
|
} from "./agents/base.ts";
|
|
@@ -24,7 +26,7 @@ export {
|
|
|
24
26
|
classifyError,
|
|
25
27
|
isRecoverable,
|
|
26
28
|
isCancellation,
|
|
27
|
-
} from "
|
|
29
|
+
} from "../protocol/errors.ts";
|
|
28
30
|
|
|
29
31
|
// Adapter contract
|
|
30
32
|
export type {
|
|
@@ -57,6 +59,7 @@ export {
|
|
|
57
59
|
export type { ClaudeSdkOptionOverrides, SdkMcpServers } from "./agents/claude-code/options.ts";
|
|
58
60
|
export type {
|
|
59
61
|
ClaudeHooksFactory,
|
|
62
|
+
ClaudeSessionEndReason,
|
|
60
63
|
ClaudeSessionExtras,
|
|
61
64
|
ClaudeToolPolicy,
|
|
62
65
|
} from "./agents/claude-code/generator-session.ts";
|
|
@@ -108,6 +111,7 @@ export {
|
|
|
108
111
|
CliNotFoundError,
|
|
109
112
|
CliProvisionError,
|
|
110
113
|
HarnessNotFoundError,
|
|
114
|
+
TurnConflictError,
|
|
111
115
|
} from "./utils/errors.ts";
|
|
112
116
|
|
|
113
117
|
// Re-export the wire contract for convenience.
|
package/src/core/presets.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { CodexAppServerAgent } from "./agents/codex-app-server/codex-app-server-
|
|
|
8
8
|
import { createCodexSdkTransformer } from "./agents/codex-sdk/adapter.ts";
|
|
9
9
|
import { CodexSdkAgent } from "./agents/codex-sdk/codex-sdk-agent.ts";
|
|
10
10
|
import { AgentRegistry } from "./agents/registry.ts";
|
|
11
|
+
import type { DiagnosticHandler } from "./diagnostics.ts";
|
|
11
12
|
import { CliProvisioner, type ProvisionOptions } from "./provision/provisioner.ts";
|
|
12
13
|
import { AgentRuntime } from "./runtime/agent-runtime.ts";
|
|
13
14
|
|
|
@@ -32,6 +33,14 @@ export interface CreateRegistryOptions {
|
|
|
32
33
|
* `resolveCliPath` stays provisioner-wired here.
|
|
33
34
|
*/
|
|
34
35
|
claudeCode?: Omit<ClaudeCodeAgentOptions, "resolveCliPath">;
|
|
36
|
+
/**
|
|
37
|
+
* Operational diagnostics port (see `EngineDiagnostic`): interrupt
|
|
38
|
+
* timeouts, resume fallbacks, sink failures — signals the engine must not
|
|
39
|
+
* fail a turn over, surfaced for the host to log/metric. Applied to the
|
|
40
|
+
* runtime and every harness that emits them; a harness-level
|
|
41
|
+
* `claudeCode.onDiagnostic` overrides for that harness.
|
|
42
|
+
*/
|
|
43
|
+
onDiagnostic?: DiagnosticHandler;
|
|
35
44
|
}
|
|
36
45
|
|
|
37
46
|
/** Build a registry with the standard harnesses wired to their adapters. */
|
|
@@ -50,7 +59,11 @@ export function createAgentRegistry(opts: CreateRegistryOptions = {}): AgentRegi
|
|
|
50
59
|
const registry = new AgentRegistry();
|
|
51
60
|
if (enabled.has("claude-code")) {
|
|
52
61
|
registry.register(
|
|
53
|
-
new ClaudeCodeAgent({
|
|
62
|
+
new ClaudeCodeAgent({
|
|
63
|
+
onDiagnostic: opts.onDiagnostic,
|
|
64
|
+
...opts.claudeCode,
|
|
65
|
+
resolveCliPath: claudeCli,
|
|
66
|
+
}),
|
|
54
67
|
createClaudeCodeTransformer,
|
|
55
68
|
);
|
|
56
69
|
}
|
|
@@ -74,5 +87,5 @@ export function createAgentRegistry(opts: CreateRegistryOptions = {}): AgentRegi
|
|
|
74
87
|
|
|
75
88
|
/** Convenience: a ready-to-use runtime with the standard harnesses. */
|
|
76
89
|
export function createAgentRuntime(opts: CreateRegistryOptions = {}): AgentRuntime {
|
|
77
|
-
return new AgentRuntime(createAgentRegistry(opts));
|
|
90
|
+
return new AgentRuntime(createAgentRegistry(opts), { onDiagnostic: opts.onDiagnostic });
|
|
78
91
|
}
|
|
@@ -27,7 +27,11 @@ export type CliTool = (typeof CLI_TOOLS)[number];
|
|
|
27
27
|
* set `provision.pins.codex` to their SDK's vendored version.
|
|
28
28
|
*/
|
|
29
29
|
export const DEFAULT_PINS: Record<CliTool, string> = {
|
|
30
|
-
|
|
30
|
+
// Held at 0.3.220: the paired CLI (2.1.220) honors `--thinking-display
|
|
31
|
+
// summarized`, so reasoning parts carry text. 2.1.233 ignores the flag and
|
|
32
|
+
// streams token-estimate placeholders instead — bump only after verifying
|
|
33
|
+
// thinking summaries still flow (see options.ts extraArgs).
|
|
34
|
+
claude: "0.3.220",
|
|
31
35
|
// 0.146.1 re-verified 2026-08-06: generate-ts snapshot carries our full
|
|
32
36
|
// surface unchanged (thread/turn/item methods, tokenUsage w/ window,
|
|
33
37
|
// approvals) and a live turn ran clean incl. the gpt-5.6-sol default.
|
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
import { type DiagnosticHandler, emitDiagnostic } from "../diagnostics.ts";
|
|
1
2
|
import { type ApiKeyStore, apiKeyStore } from "./api-key-store.ts";
|
|
2
3
|
|
|
3
4
|
/** Placeholder key handed to the agent subprocess; swapped for the real one here. */
|
|
4
5
|
export const PROXY_PLACEHOLDER_KEY = "sk-proxy-managed";
|
|
5
6
|
|
|
7
|
+
/** Minimal fetch shape (`globalThis.fetch` qualifies; injectable for tests/platforms). */
|
|
8
|
+
export type FetchLike = (
|
|
9
|
+
input: string | URL | Request,
|
|
10
|
+
init?: RequestInit & { duplex?: "half" },
|
|
11
|
+
) => Promise<Response>;
|
|
12
|
+
|
|
6
13
|
export interface AnthropicProxyOptions {
|
|
7
14
|
/** Store to resolve real credentials from. Defaults to the shared `apiKeyStore`. */
|
|
8
15
|
store?: ApiKeyStore;
|
|
9
16
|
/** Path prefix the proxy is mounted under. Default `/proxy/anthropic`. */
|
|
10
17
|
pathPrefix?: string;
|
|
18
|
+
/** Fetch implementation (test seam / platform override). Default `globalThis.fetch`. */
|
|
19
|
+
fetch?: FetchLike;
|
|
20
|
+
/** Notified on upstream 401/403 — the STORED key is bad, not the placeholder. */
|
|
21
|
+
onDiagnostic?: DiagnosticHandler;
|
|
11
22
|
}
|
|
12
23
|
|
|
13
24
|
/**
|
|
@@ -15,8 +26,15 @@ export interface AnthropicProxyOptions {
|
|
|
15
26
|
* implementing the BYOK Anthropic proxy. Mount it in any server (Bun, Hono,
|
|
16
27
|
* Workers, Node 18+). Requests come in at `{prefix}/{sessionId}/{...path}`; the
|
|
17
28
|
* handler looks up the real key for `sessionId`, swaps the `x-api-key` header,
|
|
18
|
-
* and forwards to the configured upstream
|
|
19
|
-
*
|
|
29
|
+
* and forwards to the configured upstream. Responses are NOT passed through
|
|
30
|
+
* verbatim — two host-runtime bugs require reshaping (both hit Claude Code as
|
|
31
|
+
* its API client, found in agnt's production sidecar):
|
|
32
|
+
* - SSE bodies are pumped through a `TransformStream`: Bun's HTTP server
|
|
33
|
+
* mishandles direct ReadableStream passthrough for SSE and the node
|
|
34
|
+
* client sees a premature "terminated".
|
|
35
|
+
* - Non-SSE responses drop `content-encoding`/`content-length`/
|
|
36
|
+
* `transfer-encoding`: fetch already decompressed the body, so the
|
|
37
|
+
* original headers make the client gunzip plain bytes (ZlibError).
|
|
20
38
|
*
|
|
21
39
|
* Point the agent at it with:
|
|
22
40
|
* ANTHROPIC_BASE_URL=http://localhost:PORT/proxy/anthropic/<sessionId>
|
|
@@ -64,6 +82,61 @@ export function createAnthropicProxy(
|
|
|
64
82
|
// Required when forwarding a streaming request body on fetch.
|
|
65
83
|
if (request.body) init.duplex = "half";
|
|
66
84
|
|
|
67
|
-
|
|
85
|
+
const doFetch: FetchLike = options.fetch ?? (globalThis.fetch as FetchLike);
|
|
86
|
+
let upstream: Response;
|
|
87
|
+
try {
|
|
88
|
+
upstream = await doFetch(`${entry.upstreamBaseUrl}${subPath}${url.search}`, init);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// An unreachable upstream must surface as a structured API error the
|
|
91
|
+
// agent can classify, not a host-framework 500 with an opaque body.
|
|
92
|
+
return new Response(
|
|
93
|
+
JSON.stringify({
|
|
94
|
+
type: "error",
|
|
95
|
+
error: {
|
|
96
|
+
type: "api_error",
|
|
97
|
+
message: error instanceof Error ? error.message : "proxy request failed",
|
|
98
|
+
},
|
|
99
|
+
}),
|
|
100
|
+
{ status: 502, headers: { "content-type": "application/json" } },
|
|
101
|
+
);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (upstream.status === 401 || upstream.status === 403) {
|
|
105
|
+
emitDiagnostic(options.onDiagnostic, {
|
|
106
|
+
type: "proxyUpstreamAuth",
|
|
107
|
+
sessionId,
|
|
108
|
+
message: `upstream rejected the stored key for session ${sessionId} (${upstream.status})`,
|
|
109
|
+
detail: { status: upstream.status },
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const contentType = upstream.headers.get("content-type") ?? "";
|
|
114
|
+
// Media type only — parameters (charset) stripped, case-insensitive, so
|
|
115
|
+
// `text/event-streamish` or a parameter VALUE never selects SSE handling.
|
|
116
|
+
const mediaType = (contentType.split(";", 1)[0] ?? "").trim().toLowerCase();
|
|
117
|
+
if (mediaType === "text/event-stream" && upstream.body) {
|
|
118
|
+
// Pumped through a fresh stream — see the header comment for why raw
|
|
119
|
+
// passthrough breaks on Bun. pipeTo (not a manual read/write loop) so a
|
|
120
|
+
// downstream disconnect propagates cancellation to the upstream body
|
|
121
|
+
// and the socket is released instead of held open.
|
|
122
|
+
const { readable, writable } = new TransformStream<Uint8Array, Uint8Array>();
|
|
123
|
+
void upstream.body.pipeTo(writable).catch(() => {
|
|
124
|
+
// downstream went away or upstream died — both ends settled by pipeTo
|
|
125
|
+
});
|
|
126
|
+
return new Response(readable, {
|
|
127
|
+
status: upstream.status,
|
|
128
|
+
headers: { "content-type": "text/event-stream", "cache-control": "no-cache" },
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const responseHeaders = new Headers(upstream.headers);
|
|
133
|
+
responseHeaders.delete("content-encoding");
|
|
134
|
+
responseHeaders.delete("content-length");
|
|
135
|
+
responseHeaders.delete("transfer-encoding");
|
|
136
|
+
return new Response(upstream.body, {
|
|
137
|
+
status: upstream.status,
|
|
138
|
+
statusText: upstream.statusText,
|
|
139
|
+
headers: responseHeaders,
|
|
140
|
+
});
|
|
68
141
|
};
|
|
69
142
|
}
|