@toolforge-js/sdk 0.8.7 → 0.8.8

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/dist/cli/index.js CHANGED
@@ -1,7 +1,10 @@
1
- import { C as __toESM, S as __require, _ as stopToolMessageSchema, a as AGENT_TAG_NAME, b as invariant, c as runWithRetries, d as heartbeatAckMessageSchema, f as initCommunicationMessageSchema, g as stopAgentMessageSchema, h as startToolMessageSchema, i as AGENT_STEP_TAG_NAME, l as ackMessageSchema, m as startAgentMessageSchema, n as TOOL_TAG_NAME, o as Agent, p as runnerId, r as Tool, s as exponentialBackoff, t as TOOL_HANDLER_TAG_NAME, u as baseMessageSchema, v as convertToWords, x as __commonJS, y as getErrorMessage } from "../tool-BHVNhWPh.js";
1
+ import { C as __require, S as __commonJS, _ as initCommunicationMessageSchema, a as TOOL_TAG_NAME, b as stopAgentMessageSchema, c as sessionIdStore, d as invariant, f as exponentialBackoff, g as heartbeatAckMessageSchema, h as baseMessageSchema, i as TOOL_HANDLER_TAG_NAME, l as convertToWords, m as ackMessageSchema, n as AGENT_TAG_NAME, o as Tool, p as runWithRetries, r as Agent, s as runnerIdStore, t as AGENT_STEP_TAG_NAME, u as getErrorMessage, v as startAgentMessageSchema, w as __toESM, x as stopToolMessageSchema, y as startToolMessageSchema } from "../agent-DzYymaXh.js";
2
2
  import { a as sdkServerUrlSchema, n as appServerUrlSchema, o as toolForgeConfigSchema, r as appUrlSchema } from "../config-schema-Bb9oY-Ku.js";
3
3
  import * as z$1 from "zod";
4
4
  import z from "zod";
5
+ import { P, match } from "ts-pattern";
6
+ import { setTimeout as setTimeout$1 } from "node:timers/promises";
7
+ import { nanoid } from "nanoid";
5
8
  import { createReadStream, readFileSync } from "node:fs";
6
9
  import * as path from "node:path";
7
10
  import { EventEmitter } from "node:events";
@@ -13,14 +16,12 @@ import { create } from "tar";
13
16
  import * as os from "node:os";
14
17
  import { camelCase, capitalize, kebabCase } from "es-toolkit/string";
15
18
  import esbuild from "esbuild";
16
- import { nanoid } from "nanoid";
17
- import { P, match } from "ts-pattern";
18
- import { setTimeout as setTimeout$1 } from "node:timers/promises";
19
19
  import * as prompts from "@clack/prompts";
20
20
  import picocolors from "picocolors";
21
21
  import { createAuthClient } from "better-auth/client";
22
22
  import { deviceAuthorizationClient } from "better-auth/client/plugins";
23
23
  import chokidar from "chokidar";
24
+ import * as util from "node:util";
24
25
 
25
26
  //#region ../../node_modules/.bun/commander@14.0.2/node_modules/commander/lib/error.js
26
27
  var require_error = /* @__PURE__ */ __commonJS({ "../../node_modules/.bun/commander@14.0.2/node_modules/commander/lib/error.js": ((exports) => {
@@ -3227,7 +3228,8 @@ ${agentEntries.map(({ variableName, id }) => ` '${id}': agent_${variableName},`
3227
3228
  "// @ts-nocheck",
3228
3229
  "// noinspection JSUnusedGlobalSymbols",
3229
3230
  "// This file is auto-generated by ToolForge. Do not edit manually."
3230
- ].join("\n\n") }
3231
+ ].join("\n\n") },
3232
+ external: ["@toolforge-js/sdk/components", "@toolforge-js/sdk/config"]
3231
3233
  });
3232
3234
  logger.debug("esbuild bundling completed");
3233
3235
  return {
@@ -3279,7 +3281,7 @@ function getMemoryUsage() {
3279
3281
  }
3280
3282
 
3281
3283
  //#endregion
3282
- //#region src/cli/actions/utils.ts
3284
+ //#region src/cli/lib/utils.ts
3283
3285
  async function getToolForgeConfig(configRelPath) {
3284
3286
  try {
3285
3287
  const configPath = path.resolve(process.cwd(), configRelPath);
@@ -3529,11 +3531,15 @@ async function deploy(configRelPath, debug) {
3529
3531
  prompts.outro(getErrorMessage(error));
3530
3532
  process.exit(1);
3531
3533
  });
3534
+ if (environments.length === 0) {
3535
+ prompts.outro("No production environment found. Please create a production environment in your workspace. Learn more at https://docs.tool-forge.ai/concepts/environment#production-environment");
3536
+ process.exit(0);
3537
+ }
3532
3538
  const environmentSelect = await prompts.select({
3533
3539
  message: "Select the production environment to deploy to:",
3534
- options: environments.map((env) => ({
3535
- label: `${env.environmentName} (Workspace: ${env.workspaceName})`,
3536
- value: env.id
3540
+ options: environments.map((env$1) => ({
3541
+ label: `${env$1.environmentName} (Workspace: ${env$1.workspaceName})`,
3542
+ value: env$1.id
3537
3543
  }))
3538
3544
  });
3539
3545
  if (prompts.isCancel(environmentSelect)) {
@@ -3568,7 +3574,7 @@ async function deploy(configRelPath, debug) {
3568
3574
  process.exit(1);
3569
3575
  });
3570
3576
  const deploySpinner = prompts.spinner();
3571
- const environmentName = environments.find((env) => env.id === environmentSelect)?.environmentName;
3577
+ const environmentName = environments.find((env$1) => env$1.id === environmentSelect)?.environmentName;
3572
3578
  deploySpinner.start(`Deploying tools to ${environmentName}...`);
3573
3579
  const formData = new FormData();
3574
3580
  formData.append("bundle", new File([await Bun.file(tarballPath).arrayBuffer()], path.basename(tarballPath), { type: "application/gzip" }));
@@ -3698,15 +3704,13 @@ const TOKEN_PREFIX = "tf_token--";
3698
3704
  //#endregion
3699
3705
  //#region src/internal/websocket-client.ts
3700
3706
  const optionsSchema$1 = z$1.object({
3701
- serverUrl: z$1.url(),
3702
- runnerId,
3703
- maxAcknowledgementWaitMs: z$1.number().min(0).optional().default(1e4),
3704
- maxRetries: z$1.number().min(0).optional().default(100),
3705
- retryDelayMs: z$1.number().min(100).default(1e3),
3706
- maxRetryDelayMs: z$1.number().min(1e3).optional().default(3e4),
3707
- retryJitterFactor: z$1.number().min(0).max(1).optional().default(.5),
3708
- retryBackoffMultiplier: z$1.number().min(1).optional().default(2)
3709
- });
3707
+ apiKey: z$1.string().refine((val) => val.startsWith("sk_live") || val.startsWith("pk_test") || val.startsWith(TOKEN_PREFIX)),
3708
+ tokenProviderEndpoint: z$1.url().optional(),
3709
+ sdkServerUrl: z$1.url()
3710
+ }).refine((val) => {
3711
+ if (val.apiKey.startsWith(TOKEN_PREFIX) && !val.tokenProviderEndpoint) return false;
3712
+ return true;
3713
+ }, "tokenProviderEndpoint must be present for short term apiKey");
3710
3714
  const WS_ERROR_CODES = {
3711
3715
  NORMAL_CLOSURE: 1e3,
3712
3716
  GOING_AWAY: 1001,
@@ -3725,8 +3729,18 @@ const WEB_SOCKET_CLIENT_EVENTS = {
3725
3729
  COMMUNICATION_INITIALIZED: "COMMUNICATION_INITIALIZED",
3726
3730
  STOPPED: "STOPPED"
3727
3731
  };
3732
+ const MAX_ACK_WAIT_MS = 1e4;
3733
+ const MAX_RETRIES = 100;
3734
+ const RETRY_DELAY_MS = 1e3;
3735
+ const MAX_RETRY_DELAY_MS = 3e4;
3736
+ const RETRY_JITTER_FACTOR = .5;
3737
+ const RETRY_BACKOFF_MULTIPLIER = 2;
3728
3738
  var WebSocketClient = class extends EventEmitter {
3729
3739
  #options;
3740
+ #runnerId;
3741
+ #apiKey;
3742
+ #refreshApiKeyInterval;
3743
+ #serverUrl;
3730
3744
  #forgeRunner;
3731
3745
  #logger;
3732
3746
  #connectionState = "disconnected";
@@ -3741,13 +3755,25 @@ var WebSocketClient = class extends EventEmitter {
3741
3755
  #pendingMessagesBuffer = [];
3742
3756
  #subscribeCallbacks = [];
3743
3757
  constructor(options, forgeRunner, logger) {
3744
- super();
3758
+ super({ captureRejections: true });
3745
3759
  this.#options = optionsSchema$1.parse(options);
3746
3760
  this.#forgeRunner = forgeRunner;
3747
- this.#logger = logger.child({ component: "WebSocketClient" });
3748
- this.#connectToServer();
3761
+ const runnerId = runnerIdStore.getStore();
3762
+ if (!runnerId) throw new Error("runnerId not found");
3763
+ this.#runnerId = runnerId;
3764
+ this.#logger = logger.child({
3765
+ runnerId: this.#runnerId,
3766
+ component: "WebSocketClient"
3767
+ });
3768
+ this.#apiKey = this.#options.apiKey;
3769
+ const url = new URL("/socket", this.#options.sdkServerUrl);
3770
+ url.searchParams.set("apiKey", this.#apiKey);
3771
+ url.searchParams.set("runnerId", runnerId);
3772
+ this.#serverUrl = url;
3773
+ if (this.#apiKey.startsWith(TOKEN_PREFIX)) this.#refreshApiKeyInterval = setInterval(this.#refreshApiKey.bind(this), 840 * 1e3);
3774
+ this.#connect();
3749
3775
  }
3750
- #connectToServer() {
3776
+ #connect() {
3751
3777
  if (this.#connectionState === "closing") {
3752
3778
  this.#logger.warn("start called while closing, ignoring");
3753
3779
  return;
@@ -3757,8 +3783,8 @@ var WebSocketClient = class extends EventEmitter {
3757
3783
  return;
3758
3784
  }
3759
3785
  this.#connectionState = "connecting";
3760
- this.#logger.info("connecting to SDK server at %s", this.#options.serverUrl);
3761
- this.#socket = new WebSocket(this.#options.serverUrl);
3786
+ this.#logger.info("connecting to SDK server at %s", this.#options.sdkServerUrl.toString());
3787
+ this.#socket = new WebSocket(this.#serverUrl);
3762
3788
  this.#socket.addEventListener("open", this.#handleWebSocketOpen.bind(this));
3763
3789
  this.#socket.addEventListener("close", this.#handleWebSocketClose.bind(this));
3764
3790
  this.#socket.addEventListener("message", this.#handleWebSocketMessage.bind(this));
@@ -3773,34 +3799,57 @@ var WebSocketClient = class extends EventEmitter {
3773
3799
  }
3774
3800
  }
3775
3801
  #handleWebSocketClose(event) {
3776
- this.#connectionState = "disconnected";
3802
+ if (this.#connectionState === "closing") return;
3777
3803
  this.#logger.debug({
3778
3804
  code: event.code,
3779
3805
  reason: event.reason
3780
3806
  }, "disconnected from SDK server");
3781
- if (event.code === WS_ERROR_CODES.POLICY_VIOLATION) {
3807
+ if (event.code === WS_ERROR_CODES.INVALID_FRAME_PAYLOAD_DATA && !this.#apiKey.startsWith(TOKEN_PREFIX)) {
3782
3808
  this.#logger.error("authentication failed, exiting");
3809
+ this.#connectionState = "closing";
3810
+ this.#reset();
3783
3811
  this.emit("error", /* @__PURE__ */ new Error("authentication failed"));
3784
3812
  return;
3785
3813
  }
3786
- this.#stop();
3814
+ this.#connectionState = "disconnected";
3815
+ this.#reset();
3787
3816
  this.#retryConnect();
3788
3817
  }
3789
3818
  async #retryConnect() {
3790
- if (this.#retryCount >= this.#options.maxRetries) {
3819
+ if (this.#retryCount >= MAX_RETRIES) {
3791
3820
  this.#logger.error("max retries exceeded, exiting");
3792
3821
  this.emit("error", /* @__PURE__ */ new Error("max retries exceeded"));
3793
3822
  return;
3794
3823
  }
3795
3824
  this.#retryCount += 1;
3796
- const delay = exponentialBackoff(this.#retryCount - 1, this.#options.retryDelayMs, this.#options.maxRetryDelayMs, this.#options.retryJitterFactor, this.#options.retryBackoffMultiplier);
3825
+ const delay = exponentialBackoff(this.#retryCount - 1, RETRY_DELAY_MS, MAX_RETRY_DELAY_MS, RETRY_JITTER_FACTOR, RETRY_BACKOFF_MULTIPLIER);
3797
3826
  this.#logger.debug({
3798
3827
  delay,
3799
- url: this.#options.serverUrl,
3828
+ url: this.#options.sdkServerUrl,
3800
3829
  attempt: this.#retryCount
3801
3830
  }, "retrying connection to server");
3802
3831
  await setTimeout$1(delay);
3803
- this.#connectToServer();
3832
+ await this.#refreshApiKey();
3833
+ this.#connect();
3834
+ }
3835
+ async #refreshApiKey() {
3836
+ if (!this.#options.tokenProviderEndpoint) return;
3837
+ this.#apiKey = await fetch(new URL("/token/refresh", this.#options.tokenProviderEndpoint), {
3838
+ method: "POST",
3839
+ headers: { "Content-Type": "application/json" },
3840
+ body: JSON.stringify({ token: this.#apiKey })
3841
+ }).then((res) => {
3842
+ if (!res.ok) throw new Error(`failed to refresh token: ${res.status} ${res.statusText}`);
3843
+ return res.json();
3844
+ }).then((data) => {
3845
+ const result = z$1.object({ token: z$1.string().startsWith(TOKEN_PREFIX) }).safeParse(data);
3846
+ if (!result.success) throw new Error("invalid response from token refresh");
3847
+ return result.data.token;
3848
+ }).catch((error) => {
3849
+ this.emit("error", error);
3850
+ throw error;
3851
+ });
3852
+ this.#logger.info("successfully refreshed API token");
3804
3853
  }
3805
3854
  async #handleWebSocketMessage(event) {
3806
3855
  const data = event.data;
@@ -3867,43 +3916,49 @@ var WebSocketClient = class extends EventEmitter {
3867
3916
  if (this.#heartbeatTimeout) clearTimeout(this.#heartbeatTimeout);
3868
3917
  const socket = this.#socket;
3869
3918
  invariant(socket, "socket should be present");
3870
- this.#heartbeatInterval = setInterval(() => {
3871
- socket.send(JSON.stringify({
3872
- id: nanoid(),
3873
- type: "HEARTBEAT",
3874
- timestamp: Date.now(),
3875
- data: {
3876
- clientType: "RUNNER",
3877
- cpuUsage: getCPUUsage().usage,
3878
- memoryUsage: getMemoryUsage().usage,
3879
- runnerId: this.#options.runnerId,
3880
- activeSessionsCount: this.#forgeRunner.activeSessionsCount
3881
- }
3882
- }));
3883
- }, this.#heartbeatIntervalMs);
3919
+ this.#heartbeatInterval = setInterval(this.#sendHeartbeat.bind(this), this.#heartbeatIntervalMs);
3884
3920
  this.#heartbeatTimeout = setTimeout(this.#handleHeartbeatAckMiss.bind(this), this.#heartbeatAckTimeoutMs);
3885
3921
  this.emit(WEB_SOCKET_CLIENT_EVENTS.COMMUNICATION_INITIALIZED);
3886
3922
  }
3923
+ #sendHeartbeat() {
3924
+ if (this.#socket && this.#socket.readyState === WebSocket.OPEN) this.#socket.send(JSON.stringify({
3925
+ id: nanoid(),
3926
+ type: "HEARTBEAT",
3927
+ timestamp: Date.now(),
3928
+ data: {
3929
+ clientType: "RUNNER",
3930
+ cpuUsage: getCPUUsage().usage,
3931
+ memoryUsage: getMemoryUsage().usage,
3932
+ runnerId: this.#runnerId,
3933
+ activeSessionsCount: this.#forgeRunner.activeSessionsCount
3934
+ }
3935
+ }));
3936
+ }
3887
3937
  #handleHeartbeatAck(message) {
3888
3938
  if (message.data.clientType !== "RUNNER") {
3889
3939
  this.#logger.warn("received heartbeat ack from non-runner client, ignoring");
3890
3940
  return;
3891
3941
  }
3892
- if (message.data.runnerId !== this.#options.runnerId || message.data.serverId !== this.#connectedServerId) {
3942
+ if (message.data.runnerId !== this.#runnerId || message.data.serverId !== this.#connectedServerId) {
3893
3943
  this.#logger.warn("received heartbeat ack for different runner or server, ignoring");
3894
- this.#stop();
3895
- this.#retryConnect();
3944
+ if (this.#socket && this.#socket.readyState === this.#socket.OPEN) this.#socket.close(WS_ERROR_CODES.GOING_AWAY, "received heartbeat ack for different server or runner");
3945
+ else {
3946
+ this.#connectionState = "disconnected";
3947
+ this.#reset();
3948
+ this.#retryConnect();
3949
+ }
3896
3950
  return;
3897
3951
  }
3898
3952
  const latency = Date.now() - message.timestamp;
3899
3953
  this.#logger.debug({ latency }, "heartbeat acknowledged");
3900
3954
  if (this.#heartbeatTimeout) clearTimeout(this.#heartbeatTimeout);
3901
- invariant(this.#heartbeatAckTimeoutMs, "heartbeat interval should be set");
3902
3955
  this.#heartbeatTimeout = setTimeout(this.#handleHeartbeatAckMiss.bind(this), this.#heartbeatAckTimeoutMs);
3903
3956
  }
3904
3957
  #handleHeartbeatAckMiss() {
3905
3958
  this.#logger.error("heartbeat timeout, disconnecting");
3906
- this.#stop();
3959
+ this.#connectionState = "closing";
3960
+ if (this.#socket && this.#socket.readyState === this.#socket.OPEN) this.#socket.close(WS_ERROR_CODES.GOING_AWAY, "heartbeat timeout");
3961
+ this.#reset();
3907
3962
  this.emit("error", /* @__PURE__ */ new Error("heartbeat timeout"));
3908
3963
  }
3909
3964
  #handleServerAckMessage(message) {
@@ -3922,7 +3977,7 @@ var WebSocketClient = class extends EventEmitter {
3922
3977
  const timeout = setTimeout(() => {
3923
3978
  this.#pendingServerAcks.delete(message.id);
3924
3979
  reject(/* @__PURE__ */ new Error("acknowledgement timeout"));
3925
- }, this.#options.maxAcknowledgementWaitMs);
3980
+ }, MAX_ACK_WAIT_MS);
3926
3981
  this.#pendingServerAcks.set(id, {
3927
3982
  resolve: () => {
3928
3983
  clearTimeout(timeout);
@@ -3965,27 +4020,32 @@ var WebSocketClient = class extends EventEmitter {
3965
4020
  this.#subscribeCallbacks = this.#subscribeCallbacks.filter((cb) => cb !== callback);
3966
4021
  };
3967
4022
  }
3968
- #stop() {
3969
- if (this.#heartbeatInterval) clearInterval(this.#heartbeatInterval);
3970
- if (this.#heartbeatTimeout) clearTimeout(this.#heartbeatTimeout);
3971
- if (this.#socket && this.#socket.readyState !== WebSocket.CLOSED) {
3972
- this.#socket.close(WS_ERROR_CODES.GOING_AWAY, "runner shutting down");
4023
+ #reset() {
4024
+ if (this.#refreshApiKeyInterval) {
4025
+ clearInterval(this.#refreshApiKeyInterval);
4026
+ this.#refreshApiKeyInterval = void 0;
4027
+ this.#logger.info("token refresh interval cleared");
4028
+ }
4029
+ if (this.#socket) {
3973
4030
  this.#socket.removeEventListener("open", this.#handleWebSocketOpen.bind(this));
3974
4031
  this.#socket.removeEventListener("close", this.#handleWebSocketClose.bind(this));
3975
4032
  this.#socket.removeEventListener("message", this.#handleWebSocketMessage.bind(this));
3976
4033
  this.#socket = void 0;
3977
4034
  }
4035
+ if (this.#heartbeatInterval) clearInterval(this.#heartbeatInterval);
4036
+ if (this.#heartbeatTimeout) clearTimeout(this.#heartbeatTimeout);
3978
4037
  this.#heartbeatAckTimeoutMs = void 0;
3979
4038
  this.#heartbeatIntervalMs = void 0;
3980
4039
  this.#connectedServerId = void 0;
3981
4040
  this.emit(WEB_SOCKET_CLIENT_EVENTS.STOPPED);
3982
4041
  }
3983
4042
  stop() {
3984
- this.#logger.info("gracefully stopping WebSocketClient...");
4043
+ this.#logger.info("gracefully stopping websocket client...");
3985
4044
  this.#connectionState = "closing";
3986
- this.#stop();
3987
4045
  this.#retryCount = 0;
3988
- this.#logger.info("WebSocketClient stopped");
4046
+ if (this.#socket && this.#socket.readyState === WebSocket.OPEN) this.#socket.close(WS_ERROR_CODES.GOING_AWAY, "stopping forge runner");
4047
+ this.#reset();
4048
+ this.#logger.info("websocket client stopped");
3989
4049
  }
3990
4050
  };
3991
4051
 
@@ -4014,12 +4074,9 @@ const optionsSchema = z$1.object({
4014
4074
  * spawns Tool instances, and handles file system changes to reload tools in development mode.
4015
4075
  */
4016
4076
  var ForgeRunner = class extends EventEmitter {
4017
- #id = `tf-runner:${nanoid(32)}`;
4077
+ #runnerId;
4018
4078
  #options;
4019
4079
  #logger;
4020
- #apiKey;
4021
- #sdkServerUrl;
4022
- #refreshApiKeyInterval;
4023
4080
  #forgeItems = [];
4024
4081
  #tools = {};
4025
4082
  #agents = {};
@@ -4030,24 +4087,24 @@ var ForgeRunner = class extends EventEmitter {
4030
4087
  #sessionAgents = /* @__PURE__ */ new Map();
4031
4088
  constructor(options, logger) {
4032
4089
  super({ captureRejections: true });
4090
+ const runnerId = runnerIdStore.getStore();
4091
+ if (!runnerId) throw new Error("runnerId not available");
4092
+ this.#runnerId = runnerId;
4033
4093
  this.#options = optionsSchema.parse(options);
4034
- this.#logger = logger.child({ id: this.#id });
4035
- this.#apiKey = this.#options.apiKey;
4036
- if (this.#apiKey.startsWith(TOKEN_PREFIX)) this.#refreshApiKeyInterval = setInterval(this.#refreshApiKey.bind(this), 840 * 1e3);
4037
- const url = new URL(this.#options.sdkServerUrl);
4038
- url.searchParams.set("apiKey", this.#apiKey);
4039
- url.searchParams.set("runnerId", this.#id);
4040
- url.pathname = "/socket";
4041
- this.#sdkServerUrl = url.toString();
4094
+ this.#logger = logger.child({
4095
+ runnerId: this.#runnerId,
4096
+ component: "ForgeRunner"
4097
+ });
4042
4098
  this.#webSocketClient = new WebSocketClient({
4043
- serverUrl: this.#sdkServerUrl,
4044
- runnerId: this.#id
4099
+ apiKey: this.#options.apiKey,
4100
+ sdkServerUrl: this.#options.sdkServerUrl
4045
4101
  }, this, this.#logger);
4046
4102
  this.#webSocketClient.on(WEB_SOCKET_CLIENT_EVENTS.COMMUNICATION_INITIALIZED, this.#handleWebSocketCommunicationInitialized.bind(this));
4103
+ this.#webSocketClient.on("error", this.#handleWebSocketClientError.bind(this));
4047
4104
  this.#start();
4048
4105
  }
4049
4106
  get activeSessionsCount() {
4050
- return this.#sessionTools.size;
4107
+ return this.#sessionTools.size + this.#sessionAgents.size;
4051
4108
  }
4052
4109
  async #start() {
4053
4110
  try {
@@ -4070,6 +4127,11 @@ var ForgeRunner = class extends EventEmitter {
4070
4127
  this.#logger.info("websocket communication initialized, sending forge list...");
4071
4128
  await this.#sendListForge();
4072
4129
  }
4130
+ #handleWebSocketClientError(error) {
4131
+ this.#logger.error(error, "received error from websocket client");
4132
+ this.#reset();
4133
+ this.emit("error", error);
4134
+ }
4073
4135
  async #handleWebSocketMessage(message) {
4074
4136
  const toolRunnerMessageParsed = toForgeRunnerMessages.safeParse(message);
4075
4137
  if (toolRunnerMessageParsed.success) {
@@ -4094,42 +4156,42 @@ var ForgeRunner = class extends EventEmitter {
4094
4156
  this.#logger.warn("received start tool message for unknown tool id %s, ignoring", itemId);
4095
4157
  throw new Error(`tool with id ${itemId} not found`);
4096
4158
  }
4097
- const tool = new Tool({
4098
- metadata: {
4099
- sessionId,
4100
- runnerId: this.#id,
4101
- toolName: toolDefinition.name,
4102
- toolDescription: toolDefinition.description,
4103
- toolId: itemId
4104
- },
4105
- handler: toolDefinition.handler,
4106
- webSocketClient: this.#webSocketClient
4107
- }, this.#logger);
4108
- this.#sessionTools.set(sessionId, tool);
4109
- tool.run().then((output) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4110
- id: nanoid(),
4111
- timestamp: Date.now(),
4112
- type: "TOOL_COMPLETE",
4113
- data: {
4114
- output,
4115
- sessionId,
4116
- executionContext: { type: "TOOL" }
4117
- }
4118
- }))).catch((error) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4119
- id: nanoid(),
4120
- timestamp: Date.now(),
4121
- type: "TOOL_ERROR",
4122
- data: {
4123
- error: {
4124
- message: getErrorMessage(error),
4125
- stack: error instanceof Error ? error.stack : void 0,
4126
- code: error instanceof Error && "code" in error ? String(error.code) : void 0
4127
- },
4128
- sessionId,
4129
- executionContext: { type: "TOOL" }
4130
- }
4131
- }))).finally(() => {
4132
- this.#sessionTools.delete(sessionId);
4159
+ runnerIdStore.run(this.#runnerId, () => {
4160
+ sessionIdStore.run(sessionId, () => {
4161
+ const tool = new Tool({
4162
+ toolName: toolDefinition.name,
4163
+ toolDescription: toolDefinition.description,
4164
+ toolId: itemId,
4165
+ handler: toolDefinition.handler,
4166
+ webSocketClient: this.#webSocketClient
4167
+ }, this.#logger);
4168
+ this.#sessionTools.set(sessionId, tool);
4169
+ tool.run().then((output) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4170
+ id: nanoid(),
4171
+ timestamp: Date.now(),
4172
+ type: "TOOL_COMPLETE",
4173
+ data: {
4174
+ output,
4175
+ sessionId,
4176
+ executionContext: { type: "TOOL" }
4177
+ }
4178
+ }))).catch((error) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4179
+ id: nanoid(),
4180
+ timestamp: Date.now(),
4181
+ type: "TOOL_ERROR",
4182
+ data: {
4183
+ error: {
4184
+ message: getErrorMessage(error),
4185
+ stack: error instanceof Error ? error.stack : void 0,
4186
+ code: error instanceof Error && "code" in error ? String(error.code) : void 0
4187
+ },
4188
+ sessionId,
4189
+ executionContext: { type: "TOOL" }
4190
+ }
4191
+ }))).finally(() => {
4192
+ this.#sessionTools.delete(sessionId);
4193
+ });
4194
+ });
4133
4195
  });
4134
4196
  }
4135
4197
  #handleStopToolMessage(message) {
@@ -4176,7 +4238,7 @@ var ForgeRunner = class extends EventEmitter {
4176
4238
  type: "LIST_FORGE",
4177
4239
  timestamp: Date.now(),
4178
4240
  data: {
4179
- runnerId: this.#id,
4241
+ runnerId: this.#runnerId,
4180
4242
  forgeItems: this.#forgeItems
4181
4243
  }
4182
4244
  }));
@@ -4189,39 +4251,39 @@ var ForgeRunner = class extends EventEmitter {
4189
4251
  this.#logger.warn("received start agent message for unknown agent id %s, ignoring", itemId);
4190
4252
  throw new Error(`agent with id ${itemId} not found`);
4191
4253
  }
4192
- const agent = new Agent(agentDefinition, {
4193
- metadata: {
4194
- sessionId,
4195
- runnerId: this.#id,
4196
- agentId: itemId
4197
- },
4198
- webSocketClient: this.#webSocketClient
4199
- }, this.#logger);
4200
- this.#sessionAgents.set(sessionId, agent);
4201
- agent.run().then(() => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4202
- id: nanoid(),
4203
- timestamp: Date.now(),
4204
- type: "AGENT_COMPLETE",
4205
- data: {
4206
- output: {},
4207
- sessionId,
4208
- executionContext: { type: "AGENT" }
4209
- }
4210
- }))).catch((error) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4211
- id: nanoid(),
4212
- timestamp: Date.now(),
4213
- type: "AGENT_ERROR",
4214
- data: {
4215
- error: {
4216
- message: getErrorMessage(error),
4217
- stack: error instanceof Error ? error.stack : void 0,
4218
- code: error instanceof Error && "code" in error ? String(error.code) : void 0
4219
- },
4220
- sessionId,
4221
- executionContext: { type: "AGENT" }
4222
- }
4223
- }))).finally(() => {
4224
- this.#sessionAgents.delete(sessionId);
4254
+ runnerIdStore.run(this.#runnerId, () => {
4255
+ sessionIdStore.run(sessionId, () => {
4256
+ const agent = new Agent(agentDefinition, {
4257
+ agentId: itemId,
4258
+ webSocketClient: this.#webSocketClient
4259
+ }, this.#logger);
4260
+ this.#sessionAgents.set(sessionId, agent);
4261
+ agent.run().then(() => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4262
+ id: nanoid(),
4263
+ timestamp: Date.now(),
4264
+ type: "AGENT_COMPLETE",
4265
+ data: {
4266
+ output: {},
4267
+ sessionId,
4268
+ executionContext: { type: "AGENT" }
4269
+ }
4270
+ }))).catch((error) => runWithRetries(() => this.#webSocketClient.sendToServerWithAcknowledgement({
4271
+ id: nanoid(),
4272
+ timestamp: Date.now(),
4273
+ type: "AGENT_ERROR",
4274
+ data: {
4275
+ error: {
4276
+ message: getErrorMessage(error),
4277
+ stack: error instanceof Error ? error.stack : void 0,
4278
+ code: error instanceof Error && "code" in error ? String(error.code) : void 0
4279
+ },
4280
+ sessionId,
4281
+ executionContext: { type: "AGENT" }
4282
+ }
4283
+ }))).finally(() => {
4284
+ this.#sessionAgents.delete(sessionId);
4285
+ });
4286
+ });
4225
4287
  });
4226
4288
  }
4227
4289
  #handleStopAgentMessage(message) {
@@ -4235,27 +4297,7 @@ var ForgeRunner = class extends EventEmitter {
4235
4297
  agent.stop();
4236
4298
  this.#sessionTools.delete(sessionId);
4237
4299
  }
4238
- /**
4239
- * Refreshes the API key by requesting a new token from the SDK server,
4240
- * when the apiKey is a (short lived) token used when runner is deployed
4241
- * using Tool Forge Cloud.
4242
- */
4243
- async #refreshApiKey() {
4244
- if (!this.#options.tokenProviderEndpoint) return;
4245
- const res = await fetch(new URL("/token/refresh", this.#options.tokenProviderEndpoint), {
4246
- method: "POST",
4247
- headers: { "Content-Type": "application/json" },
4248
- body: JSON.stringify({ token: this.#apiKey })
4249
- });
4250
- if (!res.ok) throw new Error(`failed to refresh token: ${res.status} ${res.statusText}`);
4251
- const data = await res.json();
4252
- const result = z$1.object({ token: z$1.string().startsWith(TOKEN_PREFIX) }).safeParse(data);
4253
- if (!result.success) throw new Error("invalid response from token refresh");
4254
- this.#apiKey = result.data.token;
4255
- this.#logger.info("successfully refreshed API token");
4256
- }
4257
- stop() {
4258
- this.#logger.info("gracefully stopping tool runner...");
4300
+ #reset() {
4259
4301
  if (this.#fsWatcher) {
4260
4302
  this.#fsWatcher.close();
4261
4303
  this.#fsWatcher = void 0;
@@ -4267,20 +4309,213 @@ var ForgeRunner = class extends EventEmitter {
4267
4309
  this.#logger.info("websocket message handler unsubscribed");
4268
4310
  }
4269
4311
  this.#webSocketClient.off(WEB_SOCKET_CLIENT_EVENTS.COMMUNICATION_INITIALIZED, this.#handleWebSocketCommunicationInitialized.bind(this));
4312
+ this.#webSocketClient.off("error", this.#handleWebSocketClientError.bind(this));
4313
+ }
4314
+ stop() {
4315
+ this.#logger.info("gracefully stopping tool runner...");
4316
+ this.#reset();
4270
4317
  this.#webSocketClient.stop();
4271
- if (this.#refreshApiKeyInterval) {
4272
- clearInterval(this.#refreshApiKeyInterval);
4273
- this.#refreshApiKeyInterval = void 0;
4274
- this.#logger.info("token refresh interval cleared");
4275
- }
4276
4318
  this.#logger.info("tool runner stopped");
4277
4319
  }
4278
4320
  };
4279
4321
 
4322
+ //#endregion
4323
+ //#region src/cli/lib/logger.ts
4324
+ const env = z$1.object({
4325
+ LOKI_HOST: z$1.url().optional(),
4326
+ LOKI_USERNAME: z$1.string().optional(),
4327
+ LOKI_PASSWORD: z$1.string().optional()
4328
+ }).parse(process.env);
4329
+ const transportTargets = [];
4330
+ if (env.LOKI_HOST) transportTargets.push({
4331
+ target: "pino-loki",
4332
+ level: "trace",
4333
+ options: {
4334
+ host: env.LOKI_HOST,
4335
+ basicAuth: env.LOKI_USERNAME && env.LOKI_PASSWORD ? {
4336
+ username: env.LOKI_USERNAME,
4337
+ password: env.LOKI_PASSWORD
4338
+ } : void 0,
4339
+ propsToLabels: [
4340
+ "app",
4341
+ "component",
4342
+ "runnerId",
4343
+ "sessionId"
4344
+ ]
4345
+ }
4346
+ });
4347
+ transportTargets.push({
4348
+ target: "pino-pretty",
4349
+ level: "trace"
4350
+ });
4351
+ const rootLogger = pino({
4352
+ level: "trace",
4353
+ transport: { targets: transportTargets },
4354
+ base: { app: "forge-runner" }
4355
+ });
4356
+ console.log = (...args) => {
4357
+ const runnerId = runnerIdStore.getStore();
4358
+ const sessionId = sessionIdStore.getStore();
4359
+ rootLogger.info({
4360
+ runnerId,
4361
+ sessionId
4362
+ }, util.format(...args));
4363
+ };
4364
+ console.info = (...args) => {
4365
+ const runnerId = runnerIdStore.getStore();
4366
+ const sessionId = sessionIdStore.getStore();
4367
+ rootLogger.info({
4368
+ runnerId,
4369
+ sessionId
4370
+ }, util.format(...args));
4371
+ };
4372
+ console.warn = (...args) => {
4373
+ const runnerId = runnerIdStore.getStore();
4374
+ const sessionId = sessionIdStore.getStore();
4375
+ rootLogger.warn({
4376
+ runnerId,
4377
+ sessionId
4378
+ }, util.format(...args));
4379
+ };
4380
+ console.error = (...args) => {
4381
+ const runnerId = runnerIdStore.getStore();
4382
+ const sessionId = sessionIdStore.getStore();
4383
+ rootLogger.error({
4384
+ runnerId,
4385
+ sessionId
4386
+ }, util.format(...args));
4387
+ };
4388
+ console.debug = (...args) => {
4389
+ const runnerId = runnerIdStore.getStore();
4390
+ const sessionId = sessionIdStore.getStore();
4391
+ rootLogger.debug({
4392
+ runnerId,
4393
+ sessionId
4394
+ }, util.format(...args));
4395
+ };
4396
+ console.trace = (...args) => {
4397
+ const runnerId = runnerIdStore.getStore();
4398
+ const sessionId = sessionIdStore.getStore();
4399
+ const stack = (/* @__PURE__ */ new Error()).stack;
4400
+ rootLogger.trace({
4401
+ runnerId,
4402
+ sessionId,
4403
+ stack
4404
+ }, util.format(...args));
4405
+ };
4406
+ console.group = (...args) => {
4407
+ const runnerId = runnerIdStore.getStore();
4408
+ const sessionId = sessionIdStore.getStore();
4409
+ rootLogger.info({
4410
+ runnerId,
4411
+ sessionId
4412
+ }, util.format("group:", ...args));
4413
+ };
4414
+ console.groupCollapsed = (...args) => {
4415
+ const runnerId = runnerIdStore.getStore();
4416
+ const sessionId = sessionIdStore.getStore();
4417
+ rootLogger.info({
4418
+ runnerId,
4419
+ sessionId
4420
+ }, util.format("group:", ...args));
4421
+ };
4422
+ console.groupEnd = () => {
4423
+ const runnerId = runnerIdStore.getStore();
4424
+ const sessionId = sessionIdStore.getStore();
4425
+ rootLogger.info({
4426
+ runnerId,
4427
+ sessionId
4428
+ }, "group end");
4429
+ };
4430
+ const timers = /* @__PURE__ */ new Map();
4431
+ console.time = (label = "default") => {
4432
+ timers.set(label, Date.now());
4433
+ };
4434
+ console.timeEnd = (label = "default") => {
4435
+ const runnerId = runnerIdStore.getStore();
4436
+ const sessionId = sessionIdStore.getStore();
4437
+ const start = timers.get(label);
4438
+ if (start) {
4439
+ const duration = Date.now() - start;
4440
+ rootLogger.info({
4441
+ runnerId,
4442
+ sessionId,
4443
+ duration
4444
+ }, `${label}: ${duration}ms`);
4445
+ timers.delete(label);
4446
+ }
4447
+ };
4448
+ console.timeLog = (label = "default", ...args) => {
4449
+ const runnerId = runnerIdStore.getStore();
4450
+ const sessionId = sessionIdStore.getStore();
4451
+ const start = timers.get(label);
4452
+ if (start) {
4453
+ const duration = Date.now() - start;
4454
+ rootLogger.info({
4455
+ runnerId,
4456
+ sessionId,
4457
+ duration
4458
+ }, util.format(`${label}: ${duration}ms`, ...args));
4459
+ }
4460
+ };
4461
+ console.assert = (condition, ...args) => {
4462
+ if (!condition) {
4463
+ const runnerId = runnerIdStore.getStore();
4464
+ const sessionId = sessionIdStore.getStore();
4465
+ rootLogger.error({
4466
+ runnerId,
4467
+ sessionId
4468
+ }, util.format("assertion failed:", ...args));
4469
+ }
4470
+ };
4471
+ console.table = (data) => {
4472
+ const runnerId = runnerIdStore.getStore();
4473
+ const sessionId = sessionIdStore.getStore();
4474
+ rootLogger.info({
4475
+ runnerId,
4476
+ sessionId,
4477
+ data
4478
+ }, "table data");
4479
+ };
4480
+ const counters = /* @__PURE__ */ new Map();
4481
+ console.count = (label = "default") => {
4482
+ const runnerId = runnerIdStore.getStore();
4483
+ const sessionId = sessionIdStore.getStore();
4484
+ const count = (counters.get(label) || 0) + 1;
4485
+ counters.set(label, count);
4486
+ rootLogger.info({
4487
+ runnerId,
4488
+ sessionId
4489
+ }, `${label}: ${count}`);
4490
+ };
4491
+ console.countReset = (label = "default") => {
4492
+ counters.delete(label);
4493
+ };
4494
+ console.clear = () => {};
4495
+ console.dir = (obj, options) => {
4496
+ const runnerId = runnerIdStore.getStore();
4497
+ const sessionId = sessionIdStore.getStore();
4498
+ rootLogger.info({
4499
+ runnerId,
4500
+ sessionId,
4501
+ obj,
4502
+ options
4503
+ }, "Dir output");
4504
+ };
4505
+ console.dirxml = (...args) => {
4506
+ const runnerId = runnerIdStore.getStore();
4507
+ const sessionId = sessionIdStore.getStore();
4508
+ rootLogger.info({
4509
+ runnerId,
4510
+ sessionId
4511
+ }, util.format(...args));
4512
+ };
4513
+
4280
4514
  //#endregion
4281
4515
  //#region src/cli/actions/start-tool-forge.ts
4282
4516
  async function startToolForge({ configRelPath, debug, mode }) {
4283
- const logger = pino({ level: debug ? "debug" : "info" });
4517
+ const runnerId = runnerIdStore.getStore();
4518
+ const logger = rootLogger.child({ runnerId }, { level: debug ? "debug" : "info" });
4284
4519
  try {
4285
4520
  const spinner = ora("loading configuration...").start();
4286
4521
  const { config, configPath } = await getToolForgeConfig(configRelPath);
@@ -4312,19 +4547,15 @@ async function startToolForge({ configRelPath, debug, mode }) {
4312
4547
  }, logger);
4313
4548
  runner.on("error", async (error) => {
4314
4549
  logger.error(error, "tool runner error - shutting down");
4315
- await runner.stop();
4316
4550
  process.exit(1);
4317
4551
  });
4318
- process.on("SIGTERM", async () => {
4552
+ async function gracefulShutdown() {
4319
4553
  logger.info("gracefully shutting down tool runner...");
4320
4554
  await runner.stop();
4321
4555
  process.exit(1);
4322
- });
4323
- process.on("SIGINT", async () => {
4324
- logger.info("gracefully shutting down tool runner...");
4325
- await runner.stop();
4326
- process.exit(1);
4327
- });
4556
+ }
4557
+ process.on("SIGTERM", gracefulShutdown);
4558
+ process.on("SIGINT", gracefulShutdown);
4328
4559
  } catch (error) {
4329
4560
  logger.error(error, "failed to start tool runner");
4330
4561
  process.exit(1);
@@ -4337,24 +4568,32 @@ const version = JSON.parse(readFileSync(path.resolve(__dirname, "../../package.j
4337
4568
  program.name("toolforge").description("Tool Forge SDK cli").version(version);
4338
4569
  program.command("dev").description("start the tool forge development server").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
4339
4570
  const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
4340
- await startToolForge({
4341
- configRelPath: z$1.string().parse(this.opts().config),
4342
- debug,
4343
- mode: "development"
4344
- }).catch((error) => {
4345
- if (error instanceof Error) console.warn("Failed to start development server:", error.message);
4346
- process.exit(1);
4571
+ const configRelPath = z$1.string().parse(this.opts().config);
4572
+ const runnerId = `tf-runner:${nanoid(32)}`;
4573
+ runnerIdStore.run(runnerId, () => {
4574
+ startToolForge({
4575
+ configRelPath,
4576
+ debug,
4577
+ mode: "development"
4578
+ }).catch((error) => {
4579
+ if (error instanceof Error) console.error(error, "failed to start toolforge runner");
4580
+ process.exit(1);
4581
+ });
4347
4582
  });
4348
4583
  });
4349
4584
  program.command("start").description("start the tool forge server in production mode").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function startServer() {
4350
4585
  const debug = z$1.boolean().optional().default(false).parse(this.opts().debug);
4351
- await startToolForge({
4352
- configRelPath: z$1.string().parse(this.opts().config),
4353
- debug,
4354
- mode: "production"
4355
- }).catch((error) => {
4356
- if (error instanceof Error) console.warn("Failed to start server:", error.message);
4357
- process.exit(1);
4586
+ const configRelPath = z$1.string().parse(this.opts().config);
4587
+ const runnerId = `tf-runner:${nanoid(32)}`;
4588
+ runnerIdStore.run(runnerId, () => {
4589
+ startToolForge({
4590
+ configRelPath,
4591
+ debug,
4592
+ mode: "production"
4593
+ }).catch((error) => {
4594
+ if (error instanceof Error) console.error(error, "failed to start toolforge runner");
4595
+ process.exit(1);
4596
+ });
4358
4597
  });
4359
4598
  });
4360
4599
  program.command("build").description("build the tools for production").option("-c, --config <path>", "path to the tool-forge config file", (val) => z$1.string().parse(val), "toolforge.config.ts").option("-d, --debug", "enable debug logging", false).action(async function() {