@playcraft/cli 0.1.86 → 0.1.87

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.
@@ -0,0 +1,149 @@
1
+ const WAITING_LOG_MS = 30_000;
2
+ const errorNames = new Set([
3
+ 'Error',
4
+ 'TypeError',
5
+ 'SyntaxError',
6
+ 'AbortError',
7
+ 'TimeoutError',
8
+ 'AggregateError',
9
+ 'HeadersTimeoutError',
10
+ 'BodyTimeoutError',
11
+ 'SocketError',
12
+ 'ConnectTimeoutError',
13
+ ]);
14
+ /** Error messages may contain request bodies or credentials; retain only bounded classifications. */
15
+ function errorChain(error) {
16
+ const chain = [];
17
+ const seen = new Set();
18
+ let current = error;
19
+ for (let depth = 0; depth < 5 && current && typeof current === 'object' && !seen.has(current); depth++) {
20
+ seen.add(current);
21
+ try {
22
+ const value = current;
23
+ const name = typeof value.name === 'string' && errorNames.has(value.name) ? value.name : undefined;
24
+ const code = typeof value.code === 'string' &&
25
+ /^(?:UND_ERR_|ERR_|E[A-Z]|CERT_|DEPTH_|UNABLE_|SELF_SIGNED_)[A-Z0-9_]{1,80}$/.test(value.code)
26
+ ? value.code
27
+ : typeof value.message === 'string' &&
28
+ /^(?:model_(?:access_denied|credential_(?:unavailable|scope_mismatch|expired|timeout|invalid)|config_required|config_denied|request_(?:deadline_exceeded|cancelled|transport_error|invalid_json|too_large))|reasoning_effort_(?:denied|unsupported))$/.test(value.message)
29
+ ? value.message
30
+ : undefined;
31
+ if (name || code)
32
+ chain.push({ name, code });
33
+ current = value.cause;
34
+ }
35
+ catch {
36
+ break;
37
+ }
38
+ }
39
+ return chain;
40
+ }
41
+ /** Per-request metadata only; observing a stalled transfer never cancels or retries it. */
42
+ export class ModelBrokerDiagnostics {
43
+ context;
44
+ started = performance.now();
45
+ stageStarted = this.started;
46
+ lastProgress = this.started;
47
+ stage = 'credential';
48
+ requestBytes = 0;
49
+ responseBytes = 0;
50
+ upstreamStatus;
51
+ abortReason;
52
+ responseIds = {};
53
+ timer;
54
+ constructor(context) {
55
+ this.context = context;
56
+ this.write('received');
57
+ this.timer = setInterval(() => {
58
+ if (performance.now() - this.lastProgress >= WAITING_LOG_MS)
59
+ this.write('waiting');
60
+ }, WAITING_LOG_MS);
61
+ this.timer.unref();
62
+ }
63
+ bind(context) {
64
+ Object.assign(this.context, context);
65
+ }
66
+ enter(stage) {
67
+ this.stage = stage;
68
+ this.stageStarted = this.lastProgress = performance.now();
69
+ this.write('stage');
70
+ }
71
+ requestChunk(bytes) {
72
+ this.requestBytes += bytes;
73
+ this.lastProgress = performance.now();
74
+ }
75
+ headers(status, headers) {
76
+ this.upstreamStatus = status;
77
+ for (const [header, field] of [
78
+ ['x-model-attempt-id', 'attemptId'],
79
+ ['x-request-id', 'proxyRequestId'],
80
+ ['eo-log-uuid', 'edgeOneRequestId'],
81
+ ['x-kong-request-id', 'kongRequestId'],
82
+ ]) {
83
+ const value = headers.get(header);
84
+ if (value && /^[A-Za-z0-9._:-]{1,128}$/.test(value))
85
+ this.responseIds[field] = value;
86
+ }
87
+ for (const [header, field] of [
88
+ ['x-kong-proxy-latency', 'kongProxyLatencyMs'],
89
+ ['x-kong-upstream-latency', 'kongUpstreamLatencyMs'],
90
+ ]) {
91
+ const value = headers.get(header);
92
+ if (value && /^\d{1,9}$/.test(value))
93
+ this.responseIds[field] = Number(value);
94
+ }
95
+ this.write('headers');
96
+ this.enter('response_body');
97
+ }
98
+ responseChunk(bytes) {
99
+ this.responseBytes += bytes;
100
+ this.lastProgress = performance.now();
101
+ if (bytes && this.responseBytes === bytes)
102
+ this.write('first_byte');
103
+ }
104
+ abort(reason) {
105
+ this.abortReason ??= reason;
106
+ }
107
+ finish(status, downstreamFinished, aborted, error) {
108
+ clearInterval(this.timer);
109
+ const outcome = error !== undefined
110
+ ? (this.abortReason ?? (aborted ? 'interrupted' : 'error'))
111
+ : this.upstreamStatus !== undefined && this.upstreamStatus >= 400
112
+ ? 'upstream_http_error'
113
+ : status >= 400
114
+ ? 'rejected'
115
+ : 'completed';
116
+ this.write('finished', {
117
+ outcome,
118
+ responseStatus: status,
119
+ downstreamFinished,
120
+ cancelled: aborted && !downstreamFinished,
121
+ ...(error !== undefined ? { errors: errorChain(error) } : {}),
122
+ });
123
+ }
124
+ write(observation, extra = {}) {
125
+ const now = performance.now();
126
+ try {
127
+ console.log(JSON.stringify({
128
+ event: 'model.broker.request',
129
+ time: Date.now(),
130
+ service: 'workspace-runtime',
131
+ ...this.context,
132
+ observation,
133
+ stage: this.stage,
134
+ elapsedMs: Math.max(0, Math.round(now - this.started)),
135
+ stageElapsedMs: Math.max(0, Math.round(now - this.stageStarted)),
136
+ idleMs: Math.max(0, Math.round(now - this.lastProgress)),
137
+ requestBytes: this.requestBytes,
138
+ responseBytes: this.responseBytes,
139
+ upstreamStatus: this.upstreamStatus,
140
+ abortReason: this.abortReason,
141
+ ...this.responseIds,
142
+ ...extra,
143
+ }));
144
+ }
145
+ catch {
146
+ /* Diagnostic output must not alter model execution. */
147
+ }
148
+ }
149
+ }
@@ -0,0 +1,59 @@
1
+ export const MODEL_REQUEST_BUDGET_MS = 150_000;
2
+ export class ModelBrokerRequestError extends Error {
3
+ status;
4
+ code;
5
+ constructor(status, code, cause) {
6
+ super(code, { cause });
7
+ this.status = status;
8
+ this.code = code;
9
+ }
10
+ }
11
+ /** Stop reading on cancellation without destroying the socket before its error response is sent. */
12
+ export function readModelRequestBody(request, signal, append) {
13
+ return new Promise((resolve, reject) => {
14
+ const cleanup = () => {
15
+ request.off('data', data);
16
+ request.off('end', end);
17
+ request.off('error', fail);
18
+ signal.removeEventListener('abort', abort);
19
+ };
20
+ const fail = (error) => {
21
+ cleanup();
22
+ request.pause();
23
+ reject(error);
24
+ };
25
+ const data = (chunk) => {
26
+ try {
27
+ append(chunk);
28
+ }
29
+ catch (error) {
30
+ fail(error);
31
+ }
32
+ };
33
+ const end = () => {
34
+ cleanup();
35
+ resolve();
36
+ };
37
+ const abort = () => fail(signal.reason);
38
+ request.on('data', data);
39
+ request.once('end', end);
40
+ request.once('error', fail);
41
+ signal.addEventListener('abort', abort, { once: true });
42
+ if (signal.aborted)
43
+ abort();
44
+ });
45
+ }
46
+ /** Public errors contain stable classifications, never arbitrary transport or response text. */
47
+ export function modelBrokerFailure(error, signal) {
48
+ if (signal.aborted) {
49
+ return signal.reason instanceof ModelBrokerRequestError
50
+ ? signal.reason
51
+ : new ModelBrokerRequestError(499, 'model_request_cancelled');
52
+ }
53
+ if (error instanceof ModelBrokerRequestError)
54
+ return error;
55
+ if (error instanceof Error && error.message === 'model_access_denied') {
56
+ return new ModelBrokerRequestError(403, error.message);
57
+ }
58
+ return new ModelBrokerRequestError(502, 'model_request_transport_error');
59
+ }