@weiyentan/opencode-plugin-awx 0.2.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/LICENSE +21 -0
- package/README.md +262 -0
- package/dist/auth.d.ts +112 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +180 -0
- package/dist/auth.js.map +1 -0
- package/dist/client.d.ts +148 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +334 -0
- package/dist/client.js.map +1 -0
- package/dist/contracts/job-detail.d.ts +141 -0
- package/dist/contracts/job-detail.d.ts.map +1 -0
- package/dist/contracts/job-detail.js +98 -0
- package/dist/contracts/job-detail.js.map +1 -0
- package/dist/contracts/sync-project.d.ts +31 -0
- package/dist/contracts/sync-project.d.ts.map +1 -0
- package/dist/contracts/sync-project.js +30 -0
- package/dist/contracts/sync-project.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +754 -0
- package/dist/index.js.map +1 -0
- package/dist/job-status.d.ts +116 -0
- package/dist/job-status.d.ts.map +1 -0
- package/dist/job-status.js +168 -0
- package/dist/job-status.js.map +1 -0
- package/dist/launch.d.ts +76 -0
- package/dist/launch.d.ts.map +1 -0
- package/dist/launch.js +133 -0
- package/dist/launch.js.map +1 -0
- package/dist/list-projects.d.ts +63 -0
- package/dist/list-projects.d.ts.map +1 -0
- package/dist/list-projects.js +84 -0
- package/dist/list-projects.js.map +1 -0
- package/dist/list-templates.d.ts +60 -0
- package/dist/list-templates.d.ts.map +1 -0
- package/dist/list-templates.js +120 -0
- package/dist/list-templates.js.map +1 -0
- package/dist/metrics.d.ts +174 -0
- package/dist/metrics.d.ts.map +1 -0
- package/dist/metrics.js +275 -0
- package/dist/metrics.js.map +1 -0
- package/dist/transforms.d.ts +52 -0
- package/dist/transforms.d.ts.map +1 -0
- package/dist/transforms.js +108 -0
- package/dist/transforms.js.map +1 -0
- package/package.json +56 -0
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* client.ts — HTTP middleware pipeline for the AWX plugin.
|
|
3
|
+
*
|
|
4
|
+
* Composes five middleware concerns into a single request pipeline:
|
|
5
|
+
* signal → timeout → breaker gate → fetch → retry/backoff
|
|
6
|
+
*
|
|
7
|
+
* ## Pipeline
|
|
8
|
+
*
|
|
9
|
+
* 1. Combine ToolContext.abort signal + timeout signal
|
|
10
|
+
* 2. Circuit breaker gate (per-tool)
|
|
11
|
+
* 3. Native fetch with Authorization header
|
|
12
|
+
* 4. Response handling: 2xx pass, 4xx no retry, 5xx exponential backoff
|
|
13
|
+
*
|
|
14
|
+
* ## Reference
|
|
15
|
+
*
|
|
16
|
+
* - ADR 0006: Connection Resilience Parameters
|
|
17
|
+
* - docs/client-middleware-design.md
|
|
18
|
+
*/
|
|
19
|
+
import { MetricsStore } from "./metrics.js";
|
|
20
|
+
/**
|
|
21
|
+
* Calculate the exponential backoff delay for a given retry attempt.
|
|
22
|
+
*
|
|
23
|
+
* Formula: base * multiplier^attempt + random(0, jitterRatio * calculated)
|
|
24
|
+
*
|
|
25
|
+
* Attempt 0 → 1000ms + 0-500ms jitter
|
|
26
|
+
* Attempt 1 → 2000ms + 0-1000ms jitter
|
|
27
|
+
* Attempt 2 → 4000ms + 0-2000ms jitter
|
|
28
|
+
*
|
|
29
|
+
* @param attempt 0-based retry attempt index
|
|
30
|
+
* @returns Delay in milliseconds
|
|
31
|
+
*/
|
|
32
|
+
export declare function calcBackoff(attempt: number): number;
|
|
33
|
+
/**
|
|
34
|
+
* Sleep for a given duration, aborting immediately if the signal fires.
|
|
35
|
+
*
|
|
36
|
+
* Used during retry backoff so that an abort (ToolContext.abort or timeout)
|
|
37
|
+
* cancels the wait immediately instead of waiting for the backoff to elapse.
|
|
38
|
+
*
|
|
39
|
+
* @param ms Duration in milliseconds
|
|
40
|
+
* @param signal Optional AbortSignal — abort listener fires, wait cancels
|
|
41
|
+
*/
|
|
42
|
+
export declare function sleepWithAbort(ms: number, signal?: AbortSignal): Promise<void>;
|
|
43
|
+
/**
|
|
44
|
+
* Create an AbortSignal that triggers after the specified timeout.
|
|
45
|
+
*
|
|
46
|
+
* Uses `setTimeout` + `AbortController` rather than `AbortSignal.timeout()`
|
|
47
|
+
* for Node 18 compatibility and to ensure compatibility with vitest fake
|
|
48
|
+
* timers (the native `AbortSignal.timeout()` uses internal Node.js timers
|
|
49
|
+
* that cannot be mocked by vitest).
|
|
50
|
+
*/
|
|
51
|
+
export declare function createTimeoutSignal(ms: number): {
|
|
52
|
+
signal: AbortSignal;
|
|
53
|
+
clear: () => void;
|
|
54
|
+
};
|
|
55
|
+
/** Options for createClient */
|
|
56
|
+
export interface ClientOptions {
|
|
57
|
+
/** Request timeout in milliseconds (default: 30_000) */
|
|
58
|
+
timeoutMs?: number;
|
|
59
|
+
/** Maximum retries on 5xx (default: 3) */
|
|
60
|
+
maxRetries?: number;
|
|
61
|
+
/** Circuit breaker trip threshold — consecutive errors before opening (default: 5) */
|
|
62
|
+
circuitBreakerThreshold?: number;
|
|
63
|
+
/** Circuit breaker cooldown in ms (default: 30_000) */
|
|
64
|
+
circuitBreakerCooldownMs?: number;
|
|
65
|
+
/**
|
|
66
|
+
* Optional shared MetricsStore for plugin-level persistence lifecycle.
|
|
67
|
+
* If not provided, a new MetricsStore is created internally.
|
|
68
|
+
* Pass the plugin-level store here so that periodic persist and
|
|
69
|
+
* init-time load are effective across all tools.
|
|
70
|
+
*/
|
|
71
|
+
metricsStore?: MetricsStore;
|
|
72
|
+
}
|
|
73
|
+
/** The AWX HTTP client returned by createClient */
|
|
74
|
+
export interface AwxClient {
|
|
75
|
+
/**
|
|
76
|
+
* Send an HTTP request through the middleware pipeline.
|
|
77
|
+
*
|
|
78
|
+
* @param toolName Per-tool circuit breaker identifier
|
|
79
|
+
* @param path API path (e.g., "/api/v2/job_templates/")
|
|
80
|
+
* @param init Fetch options (method, body, headers)
|
|
81
|
+
* @param abortSignal Optional ToolContext.abort signal for cancellation
|
|
82
|
+
*/
|
|
83
|
+
request(toolName: string, path: string, init?: RequestInit, abortSignal?: AbortSignal): Promise<Response>;
|
|
84
|
+
}
|
|
85
|
+
/** Circuit breaker state */
|
|
86
|
+
export type BreakerStateKind = "closed" | "open" | "half-open";
|
|
87
|
+
/** Per-tool circuit breaker state */
|
|
88
|
+
export interface BreakerState {
|
|
89
|
+
state: BreakerStateKind;
|
|
90
|
+
failureCount: number;
|
|
91
|
+
cooldownUntil: number | null;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Circuit breaker for per-tool request resilience.
|
|
95
|
+
*
|
|
96
|
+
* - **CLOSED**: Normal operation, requests pass through.
|
|
97
|
+
* - **OPEN**: Tripped after N consecutive failures. Requests rejected immediately.
|
|
98
|
+
* - **HALF-OPEN**: After cooldown elapses, one probe request is allowed.
|
|
99
|
+
* Success → CLOSED, Failure → OPEN again.
|
|
100
|
+
*
|
|
101
|
+
* Uses `Date.now()` for time which works with vitest fake timers
|
|
102
|
+
* (vi.useFakeTimers() mocks Date by default).
|
|
103
|
+
*/
|
|
104
|
+
export declare class CircuitBreaker {
|
|
105
|
+
private state;
|
|
106
|
+
private failureCount;
|
|
107
|
+
private cooldownUntil;
|
|
108
|
+
private readonly tripThreshold;
|
|
109
|
+
private readonly cooldownMs;
|
|
110
|
+
/**
|
|
111
|
+
* @param tripThreshold Consecutive failures before opening (default: 5)
|
|
112
|
+
* @param cooldownMs Milliseconds before transitioning OPEN → HALF-OPEN (default: 30_000)
|
|
113
|
+
*/
|
|
114
|
+
constructor(tripThreshold?: number, cooldownMs?: number);
|
|
115
|
+
/**
|
|
116
|
+
* Try to acquire permission to make a request through the breaker.
|
|
117
|
+
*
|
|
118
|
+
* Has a side effect: if OPEN and cooldown has elapsed, transitions to
|
|
119
|
+
* HALF-OPEN to allow a probe request.
|
|
120
|
+
*
|
|
121
|
+
* - OPEN: Returns false if still in cooldown. If cooldown elapsed, transitions
|
|
122
|
+
* to HALF-OPEN and returns true (probe allowed).
|
|
123
|
+
* - HALF-OPEN or CLOSED: Returns true.
|
|
124
|
+
*/
|
|
125
|
+
tryAcquire(): boolean;
|
|
126
|
+
/** Record a successful request — resets the breaker to CLOSED. */
|
|
127
|
+
recordSuccess(): void;
|
|
128
|
+
/**
|
|
129
|
+
* Record a failed request.
|
|
130
|
+
*
|
|
131
|
+
* Increments the failure counter. If the trip threshold is reached, opens the
|
|
132
|
+
* breaker and sets the cooldown timer.
|
|
133
|
+
*
|
|
134
|
+
* In HALF-OPEN state, a single failure immediately re-opens the breaker.
|
|
135
|
+
*/
|
|
136
|
+
recordFailure(): void;
|
|
137
|
+
/** Get the current breaker state (for testing/diagnostics). */
|
|
138
|
+
getState(): BreakerState;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Create an AWX HTTP client with middleware pipeline.
|
|
142
|
+
*
|
|
143
|
+
* @param baseUrl The AAP base URL (e.g., "https://example.com")
|
|
144
|
+
* @param token Bearer token (PAT) for Authorization header
|
|
145
|
+
* @param opts Optional client configuration
|
|
146
|
+
*/
|
|
147
|
+
export declare function createClient(baseUrl: string, token: string, opts?: ClientOptions): AwxClient;
|
|
148
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAC;AAkB5C;;;;;;;;;;;GAWG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAInD;AAED;;;;;;;;GAQG;AACH,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAID;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,KAAK,EAAE,MAAM,IAAI,CAAA;CAAE,CAU1F;AA2BD,+BAA+B;AAC/B,MAAM,WAAW,aAAa;IAC5B,wDAAwD;IACxD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0CAA0C;IAC1C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC,uDAAuD;IACvD,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,YAAY,CAAC;CAC7B;AAED,mDAAmD;AACnD,MAAM,WAAW,SAAS;IACxB;;;;;;;OAOG;IACH,OAAO,CACL,QAAQ,EAAE,MAAM,EAChB,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,WAAW,EAClB,WAAW,CAAC,EAAE,WAAW,GACxB,OAAO,CAAC,QAAQ,CAAC,CAAC;CACtB;AAID,4BAA4B;AAC5B,MAAM,MAAM,gBAAgB,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,CAAC;AAE/D,qCAAqC;AACrC,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,gBAAgB,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;CAC9B;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,KAAK,CAA8B;IAC3C,OAAO,CAAC,YAAY,CAAK;IACzB,OAAO,CAAC,aAAa,CAAuB;IAC5C,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IAEpC;;;OAGG;gBACS,aAAa,SAAI,EAAE,UAAU,SAAS;IAKlD;;;;;;;;;OASG;IACH,UAAU,IAAI,OAAO;IAYrB,kEAAkE;IAClE,aAAa,IAAI,IAAI;IAMrB;;;;;;;OAOG;IACH,aAAa,IAAI,IAAI;IAarB,+DAA+D;IAC/D,QAAQ,IAAI,YAAY;CAOzB;AAID;;;;;;GAMG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE,MAAM,EACf,KAAK,EAAE,MAAM,EACb,IAAI,CAAC,EAAE,aAAa,GACnB,SAAS,CAgKX"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* client.ts — HTTP middleware pipeline for the AWX plugin.
|
|
3
|
+
*
|
|
4
|
+
* Composes five middleware concerns into a single request pipeline:
|
|
5
|
+
* signal → timeout → breaker gate → fetch → retry/backoff
|
|
6
|
+
*
|
|
7
|
+
* ## Pipeline
|
|
8
|
+
*
|
|
9
|
+
* 1. Combine ToolContext.abort signal + timeout signal
|
|
10
|
+
* 2. Circuit breaker gate (per-tool)
|
|
11
|
+
* 3. Native fetch with Authorization header
|
|
12
|
+
* 4. Response handling: 2xx pass, 4xx no retry, 5xx exponential backoff
|
|
13
|
+
*
|
|
14
|
+
* ## Reference
|
|
15
|
+
*
|
|
16
|
+
* - ADR 0006: Connection Resilience Parameters
|
|
17
|
+
* - docs/client-middleware-design.md
|
|
18
|
+
*/
|
|
19
|
+
import { MetricsStore } from "./metrics.js";
|
|
20
|
+
/* ── Retry / Backoff parameters ─────────────────────────────────── */
|
|
21
|
+
/** Default max retries (3 retries = 4 total attempts) */
|
|
22
|
+
const DEFAULT_MAX_RETRIES = 3;
|
|
23
|
+
/** Base backoff delay in milliseconds */
|
|
24
|
+
const BACKOFF_BASE_MS = 1000;
|
|
25
|
+
/** Backoff multiplier (exponential) */
|
|
26
|
+
const BACKOFF_MULTIPLIER = 2;
|
|
27
|
+
/** Jitter range: 0 to 50% of calculated delay */
|
|
28
|
+
const JITTER_RATIO = 0.5;
|
|
29
|
+
/* ── Pure utility functions ────────────────────────────────────── */
|
|
30
|
+
/**
|
|
31
|
+
* Calculate the exponential backoff delay for a given retry attempt.
|
|
32
|
+
*
|
|
33
|
+
* Formula: base * multiplier^attempt + random(0, jitterRatio * calculated)
|
|
34
|
+
*
|
|
35
|
+
* Attempt 0 → 1000ms + 0-500ms jitter
|
|
36
|
+
* Attempt 1 → 2000ms + 0-1000ms jitter
|
|
37
|
+
* Attempt 2 → 4000ms + 0-2000ms jitter
|
|
38
|
+
*
|
|
39
|
+
* @param attempt 0-based retry attempt index
|
|
40
|
+
* @returns Delay in milliseconds
|
|
41
|
+
*/
|
|
42
|
+
export function calcBackoff(attempt) {
|
|
43
|
+
const base = BACKOFF_BASE_MS * Math.pow(BACKOFF_MULTIPLIER, attempt);
|
|
44
|
+
const jitter = Math.random() * base * JITTER_RATIO;
|
|
45
|
+
return Math.round(base + jitter);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Sleep for a given duration, aborting immediately if the signal fires.
|
|
49
|
+
*
|
|
50
|
+
* Used during retry backoff so that an abort (ToolContext.abort or timeout)
|
|
51
|
+
* cancels the wait immediately instead of waiting for the backoff to elapse.
|
|
52
|
+
*
|
|
53
|
+
* @param ms Duration in milliseconds
|
|
54
|
+
* @param signal Optional AbortSignal — abort listener fires, wait cancels
|
|
55
|
+
*/
|
|
56
|
+
export async function sleepWithAbort(ms, signal) {
|
|
57
|
+
if (signal?.aborted) {
|
|
58
|
+
throw signal.reason ?? new DOMException("Aborted", "AbortError");
|
|
59
|
+
}
|
|
60
|
+
return new Promise((resolve, reject) => {
|
|
61
|
+
const onAbort = () => {
|
|
62
|
+
clearTimeout(timer);
|
|
63
|
+
reject(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
64
|
+
};
|
|
65
|
+
const timer = setTimeout(() => {
|
|
66
|
+
resolve();
|
|
67
|
+
signal?.removeEventListener("abort", onAbort);
|
|
68
|
+
}, ms);
|
|
69
|
+
if (!signal)
|
|
70
|
+
return;
|
|
71
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
/* ── Node 18 Compatibility Helpers ─────────────────────────────── */
|
|
75
|
+
/**
|
|
76
|
+
* Create an AbortSignal that triggers after the specified timeout.
|
|
77
|
+
*
|
|
78
|
+
* Uses `setTimeout` + `AbortController` rather than `AbortSignal.timeout()`
|
|
79
|
+
* for Node 18 compatibility and to ensure compatibility with vitest fake
|
|
80
|
+
* timers (the native `AbortSignal.timeout()` uses internal Node.js timers
|
|
81
|
+
* that cannot be mocked by vitest).
|
|
82
|
+
*/
|
|
83
|
+
export function createTimeoutSignal(ms) {
|
|
84
|
+
const controller = new AbortController();
|
|
85
|
+
const timer = setTimeout(() => controller.abort(new DOMException("The operation timed out.", "TimeoutError")), ms);
|
|
86
|
+
return {
|
|
87
|
+
signal: controller.signal,
|
|
88
|
+
clear: () => clearTimeout(timer),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Combine multiple AbortSignals into one — aborts if ANY source signal aborts.
|
|
93
|
+
*
|
|
94
|
+
* Uses `AbortSignal.any()` on Node 20+ (where it is natively available),
|
|
95
|
+
* and falls back to manual event wiring on Node 18.
|
|
96
|
+
*/
|
|
97
|
+
function anyAbortSignal(signals) {
|
|
98
|
+
if (typeof AbortSignal.any === "function") {
|
|
99
|
+
return AbortSignal.any(signals);
|
|
100
|
+
}
|
|
101
|
+
const controller = new AbortController();
|
|
102
|
+
for (const signal of signals) {
|
|
103
|
+
if (signal.aborted) {
|
|
104
|
+
controller.abort(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
105
|
+
return controller.signal;
|
|
106
|
+
}
|
|
107
|
+
signal.addEventListener("abort", () => {
|
|
108
|
+
controller.abort(signal.reason ?? new DOMException("Aborted", "AbortError"));
|
|
109
|
+
}, { once: true });
|
|
110
|
+
}
|
|
111
|
+
return controller.signal;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Circuit breaker for per-tool request resilience.
|
|
115
|
+
*
|
|
116
|
+
* - **CLOSED**: Normal operation, requests pass through.
|
|
117
|
+
* - **OPEN**: Tripped after N consecutive failures. Requests rejected immediately.
|
|
118
|
+
* - **HALF-OPEN**: After cooldown elapses, one probe request is allowed.
|
|
119
|
+
* Success → CLOSED, Failure → OPEN again.
|
|
120
|
+
*
|
|
121
|
+
* Uses `Date.now()` for time which works with vitest fake timers
|
|
122
|
+
* (vi.useFakeTimers() mocks Date by default).
|
|
123
|
+
*/
|
|
124
|
+
export class CircuitBreaker {
|
|
125
|
+
state = "closed";
|
|
126
|
+
failureCount = 0;
|
|
127
|
+
cooldownUntil = null;
|
|
128
|
+
tripThreshold;
|
|
129
|
+
cooldownMs;
|
|
130
|
+
/**
|
|
131
|
+
* @param tripThreshold Consecutive failures before opening (default: 5)
|
|
132
|
+
* @param cooldownMs Milliseconds before transitioning OPEN → HALF-OPEN (default: 30_000)
|
|
133
|
+
*/
|
|
134
|
+
constructor(tripThreshold = 5, cooldownMs = 30_000) {
|
|
135
|
+
this.tripThreshold = tripThreshold;
|
|
136
|
+
this.cooldownMs = cooldownMs;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Try to acquire permission to make a request through the breaker.
|
|
140
|
+
*
|
|
141
|
+
* Has a side effect: if OPEN and cooldown has elapsed, transitions to
|
|
142
|
+
* HALF-OPEN to allow a probe request.
|
|
143
|
+
*
|
|
144
|
+
* - OPEN: Returns false if still in cooldown. If cooldown elapsed, transitions
|
|
145
|
+
* to HALF-OPEN and returns true (probe allowed).
|
|
146
|
+
* - HALF-OPEN or CLOSED: Returns true.
|
|
147
|
+
*/
|
|
148
|
+
tryAcquire() {
|
|
149
|
+
if (this.state === "open") {
|
|
150
|
+
const now = Date.now();
|
|
151
|
+
if (this.cooldownUntil !== null && now >= this.cooldownUntil) {
|
|
152
|
+
this.state = "half-open";
|
|
153
|
+
return true;
|
|
154
|
+
}
|
|
155
|
+
return false;
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
/** Record a successful request — resets the breaker to CLOSED. */
|
|
160
|
+
recordSuccess() {
|
|
161
|
+
this.state = "closed";
|
|
162
|
+
this.failureCount = 0;
|
|
163
|
+
this.cooldownUntil = null;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Record a failed request.
|
|
167
|
+
*
|
|
168
|
+
* Increments the failure counter. If the trip threshold is reached, opens the
|
|
169
|
+
* breaker and sets the cooldown timer.
|
|
170
|
+
*
|
|
171
|
+
* In HALF-OPEN state, a single failure immediately re-opens the breaker.
|
|
172
|
+
*/
|
|
173
|
+
recordFailure() {
|
|
174
|
+
this.failureCount++;
|
|
175
|
+
if (this.failureCount >= this.tripThreshold) {
|
|
176
|
+
this.state = "open";
|
|
177
|
+
this.cooldownUntil = Date.now() + this.cooldownMs;
|
|
178
|
+
}
|
|
179
|
+
else if (this.state === "half-open") {
|
|
180
|
+
// Half-open failure → back to open with new cooldown
|
|
181
|
+
this.state = "open";
|
|
182
|
+
this.cooldownUntil = Date.now() + this.cooldownMs;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Get the current breaker state (for testing/diagnostics). */
|
|
186
|
+
getState() {
|
|
187
|
+
return {
|
|
188
|
+
state: this.state,
|
|
189
|
+
failureCount: this.failureCount,
|
|
190
|
+
cooldownUntil: this.cooldownUntil,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/* ── Factory ───────────────────────────────────────────────────── */
|
|
195
|
+
/**
|
|
196
|
+
* Create an AWX HTTP client with middleware pipeline.
|
|
197
|
+
*
|
|
198
|
+
* @param baseUrl The AAP base URL (e.g., "https://example.com")
|
|
199
|
+
* @param token Bearer token (PAT) for Authorization header
|
|
200
|
+
* @param opts Optional client configuration
|
|
201
|
+
*/
|
|
202
|
+
export function createClient(baseUrl, token, opts) {
|
|
203
|
+
const normalizedBase = baseUrl.endsWith("/") ? baseUrl : `${baseUrl}/`;
|
|
204
|
+
const timeoutMs = opts?.timeoutMs ?? 30_000;
|
|
205
|
+
const maxRetries = opts?.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
206
|
+
const breakerThreshold = opts?.circuitBreakerThreshold ?? 5;
|
|
207
|
+
const breakerCooldownMs = opts?.circuitBreakerCooldownMs ?? 30_000;
|
|
208
|
+
// Per-tool circuit breakers
|
|
209
|
+
const breakers = new Map();
|
|
210
|
+
/** Get or create a circuit breaker for a tool */
|
|
211
|
+
function breakerFor(toolName) {
|
|
212
|
+
let breaker = breakers.get(toolName);
|
|
213
|
+
if (!breaker) {
|
|
214
|
+
breaker = new CircuitBreaker(breakerThreshold, breakerCooldownMs);
|
|
215
|
+
breakers.set(toolName, breaker);
|
|
216
|
+
}
|
|
217
|
+
return breaker;
|
|
218
|
+
}
|
|
219
|
+
// Shared metrics store instance (use injected store or create new one)
|
|
220
|
+
const metrics = opts?.metricsStore ?? new MetricsStore();
|
|
221
|
+
/** Create a spec-compliant 503 Response for when the circuit breaker is open */
|
|
222
|
+
function circuitOpenResponse() {
|
|
223
|
+
const body = JSON.stringify({
|
|
224
|
+
code: "CIRCUIT_OPEN",
|
|
225
|
+
message: "AWX circuit breaker is open — AAP may be unreachable. Try again in 30s.",
|
|
226
|
+
retryable: true,
|
|
227
|
+
});
|
|
228
|
+
return new Response(body, {
|
|
229
|
+
status: 503,
|
|
230
|
+
statusText: "Circuit breaker open",
|
|
231
|
+
headers: { "Content-Type": "application/json" },
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
return {
|
|
235
|
+
async request(toolName, path, init, abortSignal) {
|
|
236
|
+
const start = Date.now();
|
|
237
|
+
// Build full URL
|
|
238
|
+
const url = `${normalizedBase}${path.startsWith("/") ? path.slice(1) : path}`;
|
|
239
|
+
// Combine abort signals: ToolContext.abort + timeout
|
|
240
|
+
const { signal: timeoutSignal, clear: clearTimeout_ } = createTimeoutSignal(timeoutMs);
|
|
241
|
+
const combinedSignal = abortSignal
|
|
242
|
+
? anyAbortSignal([abortSignal, timeoutSignal])
|
|
243
|
+
: timeoutSignal;
|
|
244
|
+
// Build headers (plain object for testability — fetch accepts both)
|
|
245
|
+
const headers = {
|
|
246
|
+
Accept: "application/json",
|
|
247
|
+
Authorization: `Bearer ${token}`,
|
|
248
|
+
};
|
|
249
|
+
// Merge caller-supplied headers (caller wins on conflicts)
|
|
250
|
+
if (init?.headers) {
|
|
251
|
+
const callerHeaders = init.headers instanceof Headers
|
|
252
|
+
? Object.fromEntries(init.headers.entries())
|
|
253
|
+
: Array.isArray(init.headers)
|
|
254
|
+
? Object.fromEntries(init.headers)
|
|
255
|
+
: init.headers;
|
|
256
|
+
Object.assign(headers, callerHeaders);
|
|
257
|
+
}
|
|
258
|
+
const fetchInit = {
|
|
259
|
+
method: init?.method ?? "GET",
|
|
260
|
+
headers,
|
|
261
|
+
body: init?.body,
|
|
262
|
+
signal: combinedSignal,
|
|
263
|
+
};
|
|
264
|
+
const breaker = breakerFor(toolName);
|
|
265
|
+
// ── Track outcome for metrics ──
|
|
266
|
+
let recordedError = false;
|
|
267
|
+
// ── Middleware pipeline ──
|
|
268
|
+
try {
|
|
269
|
+
// ── Retry loop with exponential backoff + circuit breaker ──
|
|
270
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
271
|
+
// ── Circuit breaker gate (checked before every attempt) ──
|
|
272
|
+
if (!breaker.tryAcquire()) {
|
|
273
|
+
// Breaker is open — fail fast, no fetch
|
|
274
|
+
recordedError = true;
|
|
275
|
+
return circuitOpenResponse();
|
|
276
|
+
}
|
|
277
|
+
try {
|
|
278
|
+
const response = await fetch(url, fetchInit);
|
|
279
|
+
// 2xx — success, reset breaker, return immediately
|
|
280
|
+
if (response.ok) {
|
|
281
|
+
breaker.recordSuccess();
|
|
282
|
+
return response;
|
|
283
|
+
}
|
|
284
|
+
// Non-2xx outcome — will record error in finally block
|
|
285
|
+
recordedError = true;
|
|
286
|
+
// 4xx — client error, do NOT retry, do NOT trip circuit breaker
|
|
287
|
+
if (response.status >= 400 && response.status < 500) {
|
|
288
|
+
if (response.status === 401) {
|
|
289
|
+
metrics.recordTokenExpiry(toolName);
|
|
290
|
+
}
|
|
291
|
+
return response;
|
|
292
|
+
}
|
|
293
|
+
// 5xx — server error, record failure, retry if attempts remain
|
|
294
|
+
breaker.recordFailure();
|
|
295
|
+
if (attempt < maxRetries) {
|
|
296
|
+
const delay = calcBackoff(attempt);
|
|
297
|
+
await sleepWithAbort(delay, combinedSignal);
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
// Max retries exhausted — return the last response
|
|
301
|
+
return response;
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
// AbortError — propagate immediately, do NOT retry
|
|
305
|
+
if (err instanceof DOMException && err.name === "AbortError") {
|
|
306
|
+
throw err;
|
|
307
|
+
}
|
|
308
|
+
// Network or other error — count as failure, retry if attempts remain
|
|
309
|
+
recordedError = true;
|
|
310
|
+
breaker.recordFailure();
|
|
311
|
+
if (attempt < maxRetries) {
|
|
312
|
+
const delay = calcBackoff(attempt);
|
|
313
|
+
await sleepWithAbort(delay, combinedSignal);
|
|
314
|
+
continue;
|
|
315
|
+
}
|
|
316
|
+
// Max retries exhausted — throw the error
|
|
317
|
+
throw err;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
// Unreachable — all paths above either return or throw
|
|
321
|
+
throw new Error("Unreachable: retry loop exhausted");
|
|
322
|
+
}
|
|
323
|
+
finally {
|
|
324
|
+
const duration = Date.now() - start;
|
|
325
|
+
metrics.recordCall(toolName, duration);
|
|
326
|
+
if (recordedError) {
|
|
327
|
+
metrics.recordError(toolName);
|
|
328
|
+
}
|
|
329
|
+
clearTimeout_();
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AWX Job Detail Output Contract — v1.0
|
|
3
|
+
*
|
|
4
|
+
* Canonical TypeScript representation of the `awx_job_detail.py` v1.0 output schema.
|
|
5
|
+
* Every job-related tool MUST return output matching this contract.
|
|
6
|
+
*
|
|
7
|
+
* This file provides **both**:
|
|
8
|
+
* - Zod schemas for runtime validation (e.g., API response parsing)
|
|
9
|
+
* - Inferred TypeScript types for static type checking
|
|
10
|
+
*
|
|
11
|
+
* The contract has been verified against fixture snapshots in `tests/contracts/__snapshots__/`.
|
|
12
|
+
*
|
|
13
|
+
* ## Schema Fields
|
|
14
|
+
*
|
|
15
|
+
* - **schema_version**: Always "1.0"
|
|
16
|
+
* - **job**: Core job metadata (id, name, status, timestamps, etc.)
|
|
17
|
+
* - **related**: Resolved names (not URLs) for related AWX resources
|
|
18
|
+
* - **host_status_counts**: Count of hosts in each Ansible state — NOT `host_summary`
|
|
19
|
+
* - **derived**: Boolean flags computed from raw data — NOT `extra_vars_summary`
|
|
20
|
+
* - **warnings / errors**: String arrays for user-facing messages
|
|
21
|
+
* - **stdout** (optional): Full job stdout text
|
|
22
|
+
* - **raw_events** (optional): Raw AWX job events array
|
|
23
|
+
*
|
|
24
|
+
* ## Field Naming Convention
|
|
25
|
+
*
|
|
26
|
+
* - Use `host_status_counts` — NOT `host_summary`
|
|
27
|
+
* - Use `derived` — NOT `extra_vars_summary`
|
|
28
|
+
* - `related` fields are resolved names, not raw URLs
|
|
29
|
+
* - `job.limit` is the AWX job limit (host pattern), not a pagination value
|
|
30
|
+
*
|
|
31
|
+
* ## Snapshot Testing
|
|
32
|
+
*
|
|
33
|
+
* Fixture JSON files in `tests/fixtures/` serve as contract snapshots.
|
|
34
|
+
* When the Python `awx_job_detail.py` v1.0 output contract changes,
|
|
35
|
+
* regenerate the fixtures (see README.md for instructions) and re-run
|
|
36
|
+
* tests to verify schema compatibility.
|
|
37
|
+
*
|
|
38
|
+
* ## Regeneration
|
|
39
|
+
*
|
|
40
|
+
* To regenerate the contract snapshots after fixture changes:
|
|
41
|
+
* ```bash
|
|
42
|
+
* python3 scripts/generate-snapshots.py
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
import { z } from "zod";
|
|
46
|
+
export declare const JobCoreSchema: z.ZodObject<{
|
|
47
|
+
id: z.ZodNumber;
|
|
48
|
+
name: z.ZodString;
|
|
49
|
+
status: z.ZodString;
|
|
50
|
+
failed: z.ZodBoolean;
|
|
51
|
+
job_type: z.ZodString;
|
|
52
|
+
playbook: z.ZodString;
|
|
53
|
+
created: z.ZodString;
|
|
54
|
+
started: z.ZodNullable<z.ZodString>;
|
|
55
|
+
finished: z.ZodNullable<z.ZodString>;
|
|
56
|
+
elapsed: z.ZodNullable<z.ZodNumber>;
|
|
57
|
+
execution_node: z.ZodString;
|
|
58
|
+
controller_node: z.ZodString;
|
|
59
|
+
scm_branch: z.ZodString;
|
|
60
|
+
verbosity: z.ZodNumber;
|
|
61
|
+
forks: z.ZodNullable<z.ZodNumber>;
|
|
62
|
+
limit: z.ZodString;
|
|
63
|
+
}, z.core.$strip>;
|
|
64
|
+
export declare const RelatedSchema: z.ZodObject<{
|
|
65
|
+
inventory_name: z.ZodString;
|
|
66
|
+
project_name: z.ZodString;
|
|
67
|
+
job_template_name: z.ZodString;
|
|
68
|
+
instance_group_name: z.ZodString;
|
|
69
|
+
created_by: z.ZodString;
|
|
70
|
+
credential_names: z.ZodArray<z.ZodString>;
|
|
71
|
+
label_names: z.ZodArray<z.ZodString>;
|
|
72
|
+
}, z.core.$strip>;
|
|
73
|
+
export declare const HostStatusCountsSchema: z.ZodObject<{
|
|
74
|
+
ok: z.ZodNumber;
|
|
75
|
+
failed: z.ZodNumber;
|
|
76
|
+
skipped: z.ZodNumber;
|
|
77
|
+
changed: z.ZodNumber;
|
|
78
|
+
unreachable: z.ZodNumber;
|
|
79
|
+
}, z.core.$strip>;
|
|
80
|
+
export declare const DerivedSchema: z.ZodObject<{
|
|
81
|
+
is_successful: z.ZodBoolean;
|
|
82
|
+
is_failed: z.ZodBoolean;
|
|
83
|
+
has_unreachable_hosts: z.ZodBoolean;
|
|
84
|
+
}, z.core.$strip>;
|
|
85
|
+
export declare const JobDetailOutputSchema: z.ZodObject<{
|
|
86
|
+
schema_version: z.ZodLiteral<"1.0">;
|
|
87
|
+
job: z.ZodObject<{
|
|
88
|
+
id: z.ZodNumber;
|
|
89
|
+
name: z.ZodString;
|
|
90
|
+
status: z.ZodString;
|
|
91
|
+
failed: z.ZodBoolean;
|
|
92
|
+
job_type: z.ZodString;
|
|
93
|
+
playbook: z.ZodString;
|
|
94
|
+
created: z.ZodString;
|
|
95
|
+
started: z.ZodNullable<z.ZodString>;
|
|
96
|
+
finished: z.ZodNullable<z.ZodString>;
|
|
97
|
+
elapsed: z.ZodNullable<z.ZodNumber>;
|
|
98
|
+
execution_node: z.ZodString;
|
|
99
|
+
controller_node: z.ZodString;
|
|
100
|
+
scm_branch: z.ZodString;
|
|
101
|
+
verbosity: z.ZodNumber;
|
|
102
|
+
forks: z.ZodNullable<z.ZodNumber>;
|
|
103
|
+
limit: z.ZodString;
|
|
104
|
+
}, z.core.$strip>;
|
|
105
|
+
related: z.ZodObject<{
|
|
106
|
+
inventory_name: z.ZodString;
|
|
107
|
+
project_name: z.ZodString;
|
|
108
|
+
job_template_name: z.ZodString;
|
|
109
|
+
instance_group_name: z.ZodString;
|
|
110
|
+
created_by: z.ZodString;
|
|
111
|
+
credential_names: z.ZodArray<z.ZodString>;
|
|
112
|
+
label_names: z.ZodArray<z.ZodString>;
|
|
113
|
+
}, z.core.$strip>;
|
|
114
|
+
host_status_counts: z.ZodObject<{
|
|
115
|
+
ok: z.ZodNumber;
|
|
116
|
+
failed: z.ZodNumber;
|
|
117
|
+
skipped: z.ZodNumber;
|
|
118
|
+
changed: z.ZodNumber;
|
|
119
|
+
unreachable: z.ZodNumber;
|
|
120
|
+
}, z.core.$strip>;
|
|
121
|
+
derived: z.ZodObject<{
|
|
122
|
+
is_successful: z.ZodBoolean;
|
|
123
|
+
is_failed: z.ZodBoolean;
|
|
124
|
+
has_unreachable_hosts: z.ZodBoolean;
|
|
125
|
+
}, z.core.$strip>;
|
|
126
|
+
warnings: z.ZodArray<z.ZodString>;
|
|
127
|
+
errors: z.ZodArray<z.ZodString>;
|
|
128
|
+
stdout: z.ZodOptional<z.ZodString>;
|
|
129
|
+
raw_events: z.ZodOptional<z.ZodArray<z.ZodUnknown>>;
|
|
130
|
+
}, z.core.$strip>;
|
|
131
|
+
/** Core job metadata fields */
|
|
132
|
+
export type JobCore = z.infer<typeof JobCoreSchema>;
|
|
133
|
+
/** Resolved names for related AWX resources */
|
|
134
|
+
export type Related = z.infer<typeof RelatedSchema>;
|
|
135
|
+
/** Count of hosts in each Ansible state */
|
|
136
|
+
export type HostStatusCounts = z.infer<typeof HostStatusCountsSchema>;
|
|
137
|
+
/** Computed boolean flags */
|
|
138
|
+
export type Derived = z.infer<typeof DerivedSchema>;
|
|
139
|
+
/** Top-level JobDetailOutput contract (v1.0) */
|
|
140
|
+
export type JobDetailOutput = z.infer<typeof JobDetailOutputSchema>;
|
|
141
|
+
//# sourceMappingURL=job-detail.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"job-detail.d.ts","sourceRoot":"","sources":["../../src/contracts/job-detail.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;iBAiBxB,CAAC;AAEH,eAAO,MAAM,aAAa;;;;;;;;iBAQxB,CAAC;AAEH,eAAO,MAAM,sBAAsB;;;;;;iBAMjC,CAAC;AAEH,eAAO,MAAM,aAAa;;;;iBAIxB,CAAC;AAIH,eAAO,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAUhC,CAAC;AAIH,+BAA+B;AAC/B,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,+CAA+C;AAC/C,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,2CAA2C;AAC3C,MAAM,MAAM,gBAAgB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAEtE,6BAA6B;AAC7B,MAAM,MAAM,OAAO,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,aAAa,CAAC,CAAC;AAEpD,gDAAgD;AAChD,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC"}
|