@stackfactor/agent-utils 1.1.3 → 1.1.5

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.
@@ -6,7 +6,7 @@ import * as grpc from "@grpc/grpc-js";
6
6
  * zero or more `Progress` frames (driven by the agent's `onProgress`) followed
7
7
  * by exactly one `Result` or `Error`.
8
8
  */
9
- export declare const PROTO = "\nsyntax = \"proto3\";\npackage stackfactor.agent.v1;\n\nservice Agent {\n rpc Execute(ExecuteRequest) returns (stream Update);\n}\n\nmessage ExecuteRequest {\n string content_type = 1;\n string data_json = 2;\n string config_json = 3;\n string request_json = 4;\n int32 event = 5;\n}\n\nmessage Update {\n oneof payload {\n Progress progress = 1;\n Result result = 2;\n ErrorInfo error = 3;\n }\n}\n\nmessage Progress { int32 progress = 1; string message = 2; }\nmessage Result { string result_json = 1; }\nmessage ErrorInfo { int32 code = 1; string message = 2; }\n";
9
+ export declare const PROTO = "\nsyntax = \"proto3\";\npackage stackfactor.agent.v1;\n\nservice Agent {\n rpc Execute(ExecuteRequest) returns (stream Update);\n // Liveness/health probe served by serve() itself \u2014 no agent code required.\n // deep=true also exercises the StackFactor auth callback (BACKEND_URL).\n rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);\n}\n\nmessage ExecuteRequest {\n string content_type = 1;\n string data_json = 2;\n string config_json = 3;\n string request_json = 4;\n int32 event = 5;\n}\n\nmessage HealthCheckRequest {\n // Optional JSON carrying { authToken } for the deep backend-callback check.\n string request_json = 1;\n bool deep = 2;\n}\n\nmessage HealthCheckResponse {\n bool ok = 1;\n bool backend_reachable = 2;\n string version = 3;\n string message = 4;\n}\n\nmessage Update {\n oneof payload {\n Progress progress = 1;\n Result result = 2;\n ErrorInfo error = 3;\n }\n}\n\nmessage Progress { int32 progress = 1; string message = 2; }\nmessage Result { string result_json = 1; }\nmessage ErrorInfo { int32 code = 1; string message = 2; }\n";
10
10
  /**
11
11
  * Loads the Agent proto package. proto-loader reads from a file, so the embedded
12
12
  * schema is written to a temp path — keeps the package self-contained across the
@@ -1 +1 @@
1
- {"version":3,"file":"agentProto.d.ts","sourceRoot":"","sources":["../../src/agentProto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,smBA2BjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAO,GAWnC,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAO,IAAI,CAAC,iBACc,CAAC"}
1
+ {"version":3,"file":"agentProto.d.ts","sourceRoot":"","sources":["../../src/agentProto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,+pCA2CjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAO,GAWnC,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAO,IAAI,CAAC,iBACc,CAAC"}
@@ -52,6 +52,9 @@ package stackfactor.agent.v1;
52
52
 
53
53
  service Agent {
54
54
  rpc Execute(ExecuteRequest) returns (stream Update);
55
+ // Liveness/health probe served by serve() itself — no agent code required.
56
+ // deep=true also exercises the StackFactor auth callback (BACKEND_URL).
57
+ rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
55
58
  }
56
59
 
57
60
  message ExecuteRequest {
@@ -62,6 +65,19 @@ message ExecuteRequest {
62
65
  int32 event = 5;
63
66
  }
64
67
 
68
+ message HealthCheckRequest {
69
+ // Optional JSON carrying { authToken } for the deep backend-callback check.
70
+ string request_json = 1;
71
+ bool deep = 2;
72
+ }
73
+
74
+ message HealthCheckResponse {
75
+ bool ok = 1;
76
+ bool backend_reachable = 2;
77
+ string version = 3;
78
+ string message = 4;
79
+ }
80
+
65
81
  message Update {
66
82
  oneof payload {
67
83
  Progress progress = 1;
@@ -38,4 +38,36 @@ export interface CallAgentOptions {
38
38
  * @param request - The `ExecuteRequest` (its `request_json` carries `authToken`).
39
39
  */
40
40
  export declare const callAgent: (agentUrl: string, request: AgentExecuteRequest, options?: CallAgentOptions) => Promise<any>;
41
+ export interface CheckAgentOptions {
42
+ /** Google ID token (audience = agent URL) to pass Cloud Run ingress. */
43
+ idToken?: string;
44
+ /**
45
+ * When true, the agent additionally validates `authToken` against its
46
+ * BACKEND_URL — proving the full auth flow (not just that it's reachable).
47
+ */
48
+ deep?: boolean;
49
+ /** StackFactor session token used for the deep backend-callback check. */
50
+ authToken?: string;
51
+ /** Call deadline; defaults to 15s (a health probe should be quick). */
52
+ deadlineMs?: number;
53
+ }
54
+ export interface AgentHealth {
55
+ /** The agent answered (and, for a deep check, auth succeeded). */
56
+ ok: boolean;
57
+ /** Deep check only: the agent reached the StackFactor backend (BACKEND_URL). */
58
+ backendReachable: boolean;
59
+ /** Agent build/version string, when the agent reports one. */
60
+ version: string;
61
+ /** Human-readable detail — carries the failure reason when `ok` is false. */
62
+ message: string;
63
+ }
64
+ /**
65
+ * Probes a deployed agent's `Agent.HealthCheck` (unary). Resolves with the
66
+ * agent's health; rejects on transport/ingress errors (e.g. gRPC UNIMPLEMENTED
67
+ * when the agent predates this RPC — callers should treat that as "redeploy to
68
+ * enable", not "down").
69
+ *
70
+ * @param agentUrl - The agent's Cloud Run URL (https://…run.app).
71
+ */
72
+ export declare const checkAgent: (agentUrl: string, options?: CheckAgentOptions) => Promise<AgentHealth>;
41
73
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,gFAAgF;IAChF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqCD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,SAAS,mBAAmB,EAC5B,UAAS,gBAAqB,KAC7B,OAAO,CAAC,GAAG,CAyDV,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,gFAAgF;IAChF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqCD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,SAAS,mBAAmB,EAC5B,UAAS,gBAAqB,KAC7B,OAAO,CAAC,GAAG,CAyDV,CAAC;AAEL,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,EAAE,EAAE,OAAO,CAAC;IACZ,gFAAgF;IAChF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,UAAS,iBAAsB,KAC9B,OAAO,CAAC,WAAW,CAkClB,CAAC"}
@@ -33,7 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.callAgent = void 0;
36
+ exports.checkAgent = exports.callAgent = void 0;
37
37
  const grpc = __importStar(require("@grpc/grpc-js"));
38
38
  const agentProto_js_1 = require("./agentProto.js");
39
39
  const DEFAULT_DEADLINE_MS = 900_000;
@@ -129,3 +129,41 @@ const callAgent = (agentUrl, request, options = {}) => new Promise((resolve, rej
129
129
  stream.on("end", () => settle(() => reject(new Error("Agent stream ended without a result or error"))));
130
130
  });
131
131
  exports.callAgent = callAgent;
132
+ const DEFAULT_HEALTH_DEADLINE_MS = 15_000;
133
+ /**
134
+ * Probes a deployed agent's `Agent.HealthCheck` (unary). Resolves with the
135
+ * agent's health; rejects on transport/ingress errors (e.g. gRPC UNIMPLEMENTED
136
+ * when the agent predates this RPC — callers should treat that as "redeploy to
137
+ * enable", not "down").
138
+ *
139
+ * @param agentUrl - The agent's Cloud Run URL (https://…run.app).
140
+ */
141
+ const checkAgent = (agentUrl, options = {}) => new Promise((resolve, reject) => {
142
+ const loaded = (0, agentProto_js_1.loadAgentPackage)();
143
+ const url = new URL(agentUrl);
144
+ const address = `${url.hostname}:${url.port || "443"}`;
145
+ const client = new loaded.stackfactor.agent.v1.Agent(address, buildCredentials(options.idToken));
146
+ const request = {
147
+ request_json: JSON.stringify({ authToken: options.authToken }),
148
+ deep: !!options.deep,
149
+ };
150
+ client.HealthCheck(request, { deadline: Date.now() + (options.deadlineMs ?? DEFAULT_HEALTH_DEADLINE_MS) }, (error, response) => {
151
+ try {
152
+ client.close();
153
+ }
154
+ catch {
155
+ // ignore close errors
156
+ }
157
+ if (error) {
158
+ reject(error);
159
+ return;
160
+ }
161
+ resolve({
162
+ ok: !!response?.ok,
163
+ backendReachable: !!response?.backend_reachable,
164
+ version: response?.version ?? "",
165
+ message: response?.message ?? "",
166
+ });
167
+ });
168
+ });
169
+ exports.checkAgent = checkAgent;
@@ -3,7 +3,7 @@ import errorHandling from "./errorHandling.js";
3
3
  import langChain from "./langChain.js";
4
4
  import logger from "./logger.js";
5
5
  import { serve } from "./serve.js";
6
- import { callAgent } from "./client.js";
6
+ import { callAgent, checkAgent } from "./client.js";
7
7
  import * as runtimeContext from "./runtimeContext.js";
8
8
  export { constants };
9
9
  export { errorHandling };
@@ -11,6 +11,7 @@ export { langChain };
11
11
  export { logger };
12
12
  export { serve };
13
13
  export { callAgent };
14
- export type { AgentExecuteRequest, CallAgentOptions } from "./client.js";
14
+ export { checkAgent };
15
+ export type { AgentExecuteRequest, CallAgentOptions, CheckAgentOptions, AgentHealth, } from "./client.js";
15
16
  export { runtimeContext };
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,MAAM,oBAAoB,CAAC;AAC/C,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,MAAM,oBAAoB,CAAC;AAC/C,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/cjs/index.js CHANGED
@@ -36,7 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.runtimeContext = exports.callAgent = exports.serve = exports.logger = exports.langChain = exports.errorHandling = exports.constants = void 0;
39
+ exports.runtimeContext = exports.checkAgent = exports.callAgent = exports.serve = exports.logger = exports.langChain = exports.errorHandling = exports.constants = void 0;
40
40
  const const_js_1 = __importDefault(require("./const.js"));
41
41
  exports.constants = const_js_1.default;
42
42
  const errorHandling_js_1 = __importDefault(require("./errorHandling.js"));
@@ -49,5 +49,6 @@ const serve_js_1 = require("./serve.js");
49
49
  Object.defineProperty(exports, "serve", { enumerable: true, get: function () { return serve_js_1.serve; } });
50
50
  const client_js_1 = require("./client.js");
51
51
  Object.defineProperty(exports, "callAgent", { enumerable: true, get: function () { return client_js_1.callAgent; } });
52
+ Object.defineProperty(exports, "checkAgent", { enumerable: true, get: function () { return client_js_1.checkAgent; } });
52
53
  const runtimeContext = __importStar(require("./runtimeContext.js"));
53
54
  exports.runtimeContext = runtimeContext;
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAkDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAAI,MAAM,SAAS,EAAE,UAAS,YAAiB,KAAG,IAAI,CAAC,MAsGxE,CAAC"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAkDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAAI,MAAM,SAAS,EAAE,UAAS,YAAiB,KAAG,IAAI,CAAC,MAuIxE,CAAC"}
package/dist/cjs/serve.js CHANGED
@@ -166,8 +166,32 @@ const serve = (main, options = {}) => {
166
166
  };
167
167
  void handle();
168
168
  };
169
+ // Health probe served by the base server — agents get it for free.
170
+ // - shallow: confirms the process is up and ingress auth works (the caller
171
+ // reached this handler at all).
172
+ // - deep: additionally runs the same `authenticate` path Execute uses, which
173
+ // validates the StackFactor token against BACKEND_URL — surfacing a broken
174
+ // backend callback (the `ECONNREFUSED 127.0.0.1:443` class) before it bites.
175
+ const healthCheck = (call, callback) => {
176
+ const respond = (ok, backendReachable, message) => callback(null, {
177
+ ok,
178
+ backend_reachable: backendReachable,
179
+ version: process.env.AGENT_VERSION ?? "",
180
+ message,
181
+ });
182
+ if (!call.request?.deep) {
183
+ respond(true, false, "ok");
184
+ return;
185
+ }
186
+ void authenticate(safeParse(call.request?.request_json, {}))
187
+ .then(() => respond(true, true, "ok"))
188
+ .catch((error) => respond(false, false, error?.message ?? "auth failed"));
189
+ };
169
190
  const server = new grpc.Server();
170
- server.addService((0, agentProto_js_1.loadAgentService)(), { Execute: execute });
191
+ server.addService((0, agentProto_js_1.loadAgentService)(), {
192
+ Execute: execute,
193
+ HealthCheck: healthCheck,
194
+ });
171
195
  // Insecure: Cloud Run terminates TLS at the edge and forwards h2c to the
172
196
  // container (requires the service to be deployed with --use-http2).
173
197
  server.bindAsync(`${host}:${port}`, grpc.ServerCredentials.createInsecure(), (err, boundPort) => {
@@ -6,7 +6,7 @@ import * as grpc from "@grpc/grpc-js";
6
6
  * zero or more `Progress` frames (driven by the agent's `onProgress`) followed
7
7
  * by exactly one `Result` or `Error`.
8
8
  */
9
- export declare const PROTO = "\nsyntax = \"proto3\";\npackage stackfactor.agent.v1;\n\nservice Agent {\n rpc Execute(ExecuteRequest) returns (stream Update);\n}\n\nmessage ExecuteRequest {\n string content_type = 1;\n string data_json = 2;\n string config_json = 3;\n string request_json = 4;\n int32 event = 5;\n}\n\nmessage Update {\n oneof payload {\n Progress progress = 1;\n Result result = 2;\n ErrorInfo error = 3;\n }\n}\n\nmessage Progress { int32 progress = 1; string message = 2; }\nmessage Result { string result_json = 1; }\nmessage ErrorInfo { int32 code = 1; string message = 2; }\n";
9
+ export declare const PROTO = "\nsyntax = \"proto3\";\npackage stackfactor.agent.v1;\n\nservice Agent {\n rpc Execute(ExecuteRequest) returns (stream Update);\n // Liveness/health probe served by serve() itself \u2014 no agent code required.\n // deep=true also exercises the StackFactor auth callback (BACKEND_URL).\n rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);\n}\n\nmessage ExecuteRequest {\n string content_type = 1;\n string data_json = 2;\n string config_json = 3;\n string request_json = 4;\n int32 event = 5;\n}\n\nmessage HealthCheckRequest {\n // Optional JSON carrying { authToken } for the deep backend-callback check.\n string request_json = 1;\n bool deep = 2;\n}\n\nmessage HealthCheckResponse {\n bool ok = 1;\n bool backend_reachable = 2;\n string version = 3;\n string message = 4;\n}\n\nmessage Update {\n oneof payload {\n Progress progress = 1;\n Result result = 2;\n ErrorInfo error = 3;\n }\n}\n\nmessage Progress { int32 progress = 1; string message = 2; }\nmessage Result { string result_json = 1; }\nmessage ErrorInfo { int32 code = 1; string message = 2; }\n";
10
10
  /**
11
11
  * Loads the Agent proto package. proto-loader reads from a file, so the embedded
12
12
  * schema is written to a temp path — keeps the package self-contained across the
@@ -1 +1 @@
1
- {"version":3,"file":"agentProto.d.ts","sourceRoot":"","sources":["../../src/agentProto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,smBA2BjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAO,GAWnC,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAO,IAAI,CAAC,iBACc,CAAC"}
1
+ {"version":3,"file":"agentProto.d.ts","sourceRoot":"","sources":["../../src/agentProto.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;;GAMG;AACH,eAAO,MAAM,KAAK,+pCA2CjB,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,QAAO,GAWnC,CAAC;AAEF,oEAAoE;AACpE,eAAO,MAAM,gBAAgB,QAAO,IAAI,CAAC,iBACc,CAAC"}
@@ -16,6 +16,9 @@ package stackfactor.agent.v1;
16
16
 
17
17
  service Agent {
18
18
  rpc Execute(ExecuteRequest) returns (stream Update);
19
+ // Liveness/health probe served by serve() itself — no agent code required.
20
+ // deep=true also exercises the StackFactor auth callback (BACKEND_URL).
21
+ rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse);
19
22
  }
20
23
 
21
24
  message ExecuteRequest {
@@ -26,6 +29,19 @@ message ExecuteRequest {
26
29
  int32 event = 5;
27
30
  }
28
31
 
32
+ message HealthCheckRequest {
33
+ // Optional JSON carrying { authToken } for the deep backend-callback check.
34
+ string request_json = 1;
35
+ bool deep = 2;
36
+ }
37
+
38
+ message HealthCheckResponse {
39
+ bool ok = 1;
40
+ bool backend_reachable = 2;
41
+ string version = 3;
42
+ string message = 4;
43
+ }
44
+
29
45
  message Update {
30
46
  oneof payload {
31
47
  Progress progress = 1;
@@ -38,4 +38,36 @@ export interface CallAgentOptions {
38
38
  * @param request - The `ExecuteRequest` (its `request_json` carries `authToken`).
39
39
  */
40
40
  export declare const callAgent: (agentUrl: string, request: AgentExecuteRequest, options?: CallAgentOptions) => Promise<any>;
41
+ export interface CheckAgentOptions {
42
+ /** Google ID token (audience = agent URL) to pass Cloud Run ingress. */
43
+ idToken?: string;
44
+ /**
45
+ * When true, the agent additionally validates `authToken` against its
46
+ * BACKEND_URL — proving the full auth flow (not just that it's reachable).
47
+ */
48
+ deep?: boolean;
49
+ /** StackFactor session token used for the deep backend-callback check. */
50
+ authToken?: string;
51
+ /** Call deadline; defaults to 15s (a health probe should be quick). */
52
+ deadlineMs?: number;
53
+ }
54
+ export interface AgentHealth {
55
+ /** The agent answered (and, for a deep check, auth succeeded). */
56
+ ok: boolean;
57
+ /** Deep check only: the agent reached the StackFactor backend (BACKEND_URL). */
58
+ backendReachable: boolean;
59
+ /** Agent build/version string, when the agent reports one. */
60
+ version: string;
61
+ /** Human-readable detail — carries the failure reason when `ok` is false. */
62
+ message: string;
63
+ }
64
+ /**
65
+ * Probes a deployed agent's `Agent.HealthCheck` (unary). Resolves with the
66
+ * agent's health; rejects on transport/ingress errors (e.g. gRPC UNIMPLEMENTED
67
+ * when the agent predates this RPC — callers should treat that as "redeploy to
68
+ * enable", not "down").
69
+ *
70
+ * @param agentUrl - The agent's Cloud Run URL (https://…run.app).
71
+ */
72
+ export declare const checkAgent: (agentUrl: string, options?: CheckAgentOptions) => Promise<AgentHealth>;
41
73
  //# sourceMappingURL=client.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,gFAAgF;IAChF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqCD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,SAAS,mBAAmB,EAC5B,UAAS,gBAAqB,KAC7B,OAAO,CAAC,GAAG,CAyDV,CAAC"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/client.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,WAAW,mBAAmB;IAClC,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAgB;IAC/B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,KAAK,IAAI,CAAC;IACrE,gFAAgF;IAChF,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAqCD;;;;;;;;GAQG;AACH,eAAO,MAAM,SAAS,GACpB,UAAU,MAAM,EAChB,SAAS,mBAAmB,EAC5B,UAAS,gBAAqB,KAC7B,OAAO,CAAC,GAAG,CAyDV,CAAC;AAEL,MAAM,WAAW,iBAAiB;IAChC,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,0EAA0E;IAC1E,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,EAAE,EAAE,OAAO,CAAC;IACZ,gFAAgF;IAChF,gBAAgB,EAAE,OAAO,CAAC;IAC1B,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,6EAA6E;IAC7E,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GACrB,UAAU,MAAM,EAChB,UAAS,iBAAsB,KAC9B,OAAO,CAAC,WAAW,CAkClB,CAAC"}
@@ -92,3 +92,40 @@ export const callAgent = (agentUrl, request, options = {}) => new Promise((resol
92
92
  stream.on("error", (error) => settle(() => reject(error)));
93
93
  stream.on("end", () => settle(() => reject(new Error("Agent stream ended without a result or error"))));
94
94
  });
95
+ const DEFAULT_HEALTH_DEADLINE_MS = 15_000;
96
+ /**
97
+ * Probes a deployed agent's `Agent.HealthCheck` (unary). Resolves with the
98
+ * agent's health; rejects on transport/ingress errors (e.g. gRPC UNIMPLEMENTED
99
+ * when the agent predates this RPC — callers should treat that as "redeploy to
100
+ * enable", not "down").
101
+ *
102
+ * @param agentUrl - The agent's Cloud Run URL (https://…run.app).
103
+ */
104
+ export const checkAgent = (agentUrl, options = {}) => new Promise((resolve, reject) => {
105
+ const loaded = loadAgentPackage();
106
+ const url = new URL(agentUrl);
107
+ const address = `${url.hostname}:${url.port || "443"}`;
108
+ const client = new loaded.stackfactor.agent.v1.Agent(address, buildCredentials(options.idToken));
109
+ const request = {
110
+ request_json: JSON.stringify({ authToken: options.authToken }),
111
+ deep: !!options.deep,
112
+ };
113
+ client.HealthCheck(request, { deadline: Date.now() + (options.deadlineMs ?? DEFAULT_HEALTH_DEADLINE_MS) }, (error, response) => {
114
+ try {
115
+ client.close();
116
+ }
117
+ catch {
118
+ // ignore close errors
119
+ }
120
+ if (error) {
121
+ reject(error);
122
+ return;
123
+ }
124
+ resolve({
125
+ ok: !!response?.ok,
126
+ backendReachable: !!response?.backend_reachable,
127
+ version: response?.version ?? "",
128
+ message: response?.message ?? "",
129
+ });
130
+ });
131
+ });
@@ -3,7 +3,7 @@ import errorHandling from "./errorHandling.js";
3
3
  import langChain from "./langChain.js";
4
4
  import logger from "./logger.js";
5
5
  import { serve } from "./serve.js";
6
- import { callAgent } from "./client.js";
6
+ import { callAgent, checkAgent } from "./client.js";
7
7
  import * as runtimeContext from "./runtimeContext.js";
8
8
  export { constants };
9
9
  export { errorHandling };
@@ -11,6 +11,7 @@ export { langChain };
11
11
  export { logger };
12
12
  export { serve };
13
13
  export { callAgent };
14
- export type { AgentExecuteRequest, CallAgentOptions } from "./client.js";
14
+ export { checkAgent };
15
+ export type { AgentExecuteRequest, CallAgentOptions, CheckAgentOptions, AgentHealth, } from "./client.js";
15
16
  export { runtimeContext };
16
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,MAAM,oBAAoB,CAAC;AAC/C,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEzE,OAAO,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,SAAS,MAAM,YAAY,CAAC;AACnC,OAAO,aAAa,MAAM,oBAAoB,CAAC;AAC/C,OAAO,SAAS,MAAM,gBAAgB,CAAC;AACvC,OAAO,MAAM,MAAM,aAAa,CAAC;AACjC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,cAAc,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,aAAa,EAAE,CAAC;AAEzB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,MAAM,EAAE,CAAC;AAElB,OAAO,EAAE,KAAK,EAAE,CAAC;AAEjB,OAAO,EAAE,SAAS,EAAE,CAAC;AAErB,OAAO,EAAE,UAAU,EAAE,CAAC;AAEtB,YAAY,EACV,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,WAAW,GACZ,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,cAAc,EAAE,CAAC"}
package/dist/esm/index.js CHANGED
@@ -3,7 +3,7 @@ import errorHandling from "./errorHandling.js";
3
3
  import langChain from "./langChain.js";
4
4
  import logger from "./logger.js";
5
5
  import { serve } from "./serve.js";
6
- import { callAgent } from "./client.js";
6
+ import { callAgent, checkAgent } from "./client.js";
7
7
  import * as runtimeContext from "./runtimeContext.js";
8
8
  export { constants };
9
9
  export { errorHandling };
@@ -11,4 +11,5 @@ export { langChain };
11
11
  export { logger };
12
12
  export { serve };
13
13
  export { callAgent };
14
+ export { checkAgent };
14
15
  export { runtimeContext };
@@ -1 +1 @@
1
- {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAkDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAAI,MAAM,SAAS,EAAE,UAAS,YAAiB,KAAG,IAAI,CAAC,MAsGxE,CAAC"}
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAMtC;;;;;GAKG;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,MAAM,CAAC,EAAE,WAAW,KAAK,OAAO,CAAC,GAAG,CAAC,CAAC;AAE/D;;;;GAIG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,GAAG,KAAK,OAAO,CAAC;IAAE,IAAI,EAAE,GAAG,CAAC;IAAC,MAAM,EAAE,GAAG,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,YAAY;IAC3B,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,YAAY,CAAC,EAAE,aAAa,CAAC;CAC9B;AAkDD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,KAAK,GAAI,MAAM,SAAS,EAAE,UAAS,YAAiB,KAAG,IAAI,CAAC,MAuIxE,CAAC"}
package/dist/esm/serve.js CHANGED
@@ -127,8 +127,32 @@ export const serve = (main, options = {}) => {
127
127
  };
128
128
  void handle();
129
129
  };
130
+ // Health probe served by the base server — agents get it for free.
131
+ // - shallow: confirms the process is up and ingress auth works (the caller
132
+ // reached this handler at all).
133
+ // - deep: additionally runs the same `authenticate` path Execute uses, which
134
+ // validates the StackFactor token against BACKEND_URL — surfacing a broken
135
+ // backend callback (the `ECONNREFUSED 127.0.0.1:443` class) before it bites.
136
+ const healthCheck = (call, callback) => {
137
+ const respond = (ok, backendReachable, message) => callback(null, {
138
+ ok,
139
+ backend_reachable: backendReachable,
140
+ version: process.env.AGENT_VERSION ?? "",
141
+ message,
142
+ });
143
+ if (!call.request?.deep) {
144
+ respond(true, false, "ok");
145
+ return;
146
+ }
147
+ void authenticate(safeParse(call.request?.request_json, {}))
148
+ .then(() => respond(true, true, "ok"))
149
+ .catch((error) => respond(false, false, error?.message ?? "auth failed"));
150
+ };
130
151
  const server = new grpc.Server();
131
- server.addService(loadAgentService(), { Execute: execute });
152
+ server.addService(loadAgentService(), {
153
+ Execute: execute,
154
+ HealthCheck: healthCheck,
155
+ });
132
156
  // Insecure: Cloud Run terminates TLS at the edge and forwards h2c to the
133
157
  // container (requires the service to be deployed with --use-http2).
134
158
  server.bindAsync(`${host}:${port}`, grpc.ServerCredentials.createInsecure(), (err, boundPort) => {
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "publishConfig": {
4
4
  "access": "public"
5
5
  },
6
- "version": "1.1.3",
6
+ "version": "1.1.5",
7
7
  "description": "",
8
8
  "main": "dist/cjs/index.js",
9
9
  "module": "dist/esm/index.js",
@@ -46,7 +46,7 @@
46
46
  "@langchain/google-genai": "^2.1.26",
47
47
  "@langchain/openai": "^1.3.0",
48
48
  "@opentelemetry/api": "^1.9.0",
49
- "@stackfactor/client-api": "^1.1.241",
49
+ "@stackfactor/client-api": "^1.1.242",
50
50
  "langchain": "^1.2.35",
51
51
  "mongoose": "^9.3.1",
52
52
  "openai": "^6.32.0",