@sf-explorer/agentforce-service 1.0.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.
@@ -0,0 +1,181 @@
1
+ import { file, directory } from "@polycuber/script.cli";
2
+ import { join } from "node:path";
3
+ import { Agent, ScriptAgent } from "@salesforce/agents";
4
+ import { SfProject } from "@salesforce/core";
5
+ import { initializeContext } from "./context.js";
6
+ export function registerAgentsRoutes(router, context) {
7
+ const bundlesDir = join(context.projectDir, "force-app", "main", "default", "aiAuthoringBundles");
8
+ async function ensureInitialized() {
9
+ if (!context.orgInfo) {
10
+ await initializeContext(context);
11
+ }
12
+ }
13
+ /**
14
+ * POST /compile
15
+ * Body: { agentBundleName }
16
+ */
17
+ router.post("/compile", async (req, res) => {
18
+ try {
19
+ await ensureInitialized();
20
+ const agentBundleName = req.body.agentBundleName ?? req.body.aabName;
21
+ if (!agentBundleName) {
22
+ res.status(400).json({ error: "Missing required field: agentBundleName" });
23
+ return;
24
+ }
25
+ const project = await SfProject.resolve(context.projectDir);
26
+ const scriptAgent = await Agent.init({
27
+ connection: context.sfConn,
28
+ project,
29
+ aabName: agentBundleName,
30
+ });
31
+ if (!(scriptAgent instanceof ScriptAgent)) {
32
+ res.status(400).json({
33
+ error: "Agent is not a ScriptAgent (compile only supports script agents)",
34
+ });
35
+ return;
36
+ }
37
+ const result = await scriptAgent.compile();
38
+ res.json(result);
39
+ }
40
+ catch (err) {
41
+ res.status(500).json({
42
+ error: err instanceof Error ? err.message : "Compile failed",
43
+ });
44
+ }
45
+ });
46
+ /**
47
+ * GET /agents
48
+ * Query: ?source=local|remote|all (default: all)
49
+ */
50
+ router.get("/agents", async (req, res) => {
51
+ try {
52
+ await ensureInitialized();
53
+ const source = req.query.source ?? "all";
54
+ const project = await SfProject.resolve(context.projectDir);
55
+ if (source === "local") {
56
+ const aabNames = await Agent.list(project);
57
+ res.json({ agents: aabNames, source: "local" });
58
+ return;
59
+ }
60
+ if (source === "remote") {
61
+ const bots = await Agent.listRemote(context.sfConn);
62
+ res.json({ agents: bots, source: "remote" });
63
+ return;
64
+ }
65
+ const previewable = await Agent.listPreviewable(context.sfConn, project);
66
+ res.json({ agents: previewable, source: "all" });
67
+ }
68
+ catch (err) {
69
+ res.status(500).json({
70
+ error: err instanceof Error ? err.message : "Retrieve agents failed",
71
+ });
72
+ }
73
+ });
74
+ /**
75
+ * GET /agent/:developerName
76
+ */
77
+ router.get("/agent/:developerName", (req, res) => {
78
+ try {
79
+ const { developerName } = req.params;
80
+ const bundlePath = join(bundlesDir, developerName);
81
+ const result = {};
82
+ const agentPath = join(bundlePath, `${developerName}.agent`);
83
+ const metaPath = join(bundlePath, `${developerName}.bundle-meta.xml`);
84
+ if (file.exists(agentPath)) {
85
+ const content = file.read.text(agentPath);
86
+ if (content)
87
+ result[`aiAuthoringBundles/${developerName}/${developerName}.agent`] = content;
88
+ }
89
+ if (file.exists(metaPath)) {
90
+ const content = file.read.text(metaPath);
91
+ if (content)
92
+ result[`aiAuthoringBundles/${developerName}/${developerName}.bundle-meta.xml`] = content;
93
+ }
94
+ if (Object.keys(result).length === 0) {
95
+ res.status(404).json({
96
+ error: `Agent not found: ${developerName}`,
97
+ });
98
+ return;
99
+ }
100
+ res.json(result);
101
+ }
102
+ catch (err) {
103
+ res.status(500).json({
104
+ error: err instanceof Error ? err.message : "Get agent failed",
105
+ });
106
+ }
107
+ });
108
+ /**
109
+ * POST /agent
110
+ * Body: { developerName, agentContent, bundleMetaContent? }
111
+ */
112
+ router.post("/agent", (req, res) => {
113
+ try {
114
+ const { developerName, agentContent, bundleMetaContent } = req.body;
115
+ if (!developerName || !agentContent) {
116
+ res.status(400).json({
117
+ error: "Missing required fields: developerName, agentContent",
118
+ });
119
+ return;
120
+ }
121
+ const bundlePath = join(bundlesDir, developerName);
122
+ if (directory.exists(bundlePath)) {
123
+ res.status(409).json({
124
+ error: `Agent already exists: ${developerName}`,
125
+ });
126
+ return;
127
+ }
128
+ directory.make(bundlePath);
129
+ file.write.text(join(bundlePath, `${developerName}.agent`), agentContent);
130
+ if (bundleMetaContent) {
131
+ file.write.text(join(bundlePath, `${developerName}.bundle-meta.xml`), bundleMetaContent);
132
+ }
133
+ else {
134
+ const defaultMeta = `<?xml version="1.0" encoding="UTF-8"?>
135
+ <AiAuthoringBundle xmlns="http://soap.sforce.com/2006/04/metadata">
136
+ <bundleType>AGENT</bundleType>
137
+ </AiAuthoringBundle>`;
138
+ file.write.text(join(bundlePath, `${developerName}.bundle-meta.xml`), defaultMeta);
139
+ }
140
+ res.status(201).json({ success: true, developerName });
141
+ }
142
+ catch (err) {
143
+ res.status(500).json({
144
+ error: err instanceof Error ? err.message : "Create agent failed",
145
+ });
146
+ }
147
+ });
148
+ /**
149
+ * PUT /agent/:developerName
150
+ * Body: { agentContent, bundleMetaContent? }
151
+ */
152
+ router.put("/agent/:developerName", (req, res) => {
153
+ try {
154
+ const { developerName } = req.params;
155
+ const { agentContent, bundleMetaContent } = req.body;
156
+ if (!agentContent) {
157
+ res.status(400).json({
158
+ error: "Missing required field: agentContent",
159
+ });
160
+ return;
161
+ }
162
+ const bundlePath = join(bundlesDir, developerName);
163
+ if (!directory.exists(bundlePath)) {
164
+ res.status(404).json({
165
+ error: `Agent not found: ${developerName}. Use POST /agent to create.`,
166
+ });
167
+ return;
168
+ }
169
+ file.write.text(join(bundlePath, `${developerName}.agent`), agentContent);
170
+ if (bundleMetaContent) {
171
+ file.write.text(join(bundlePath, `${developerName}.bundle-meta.xml`), bundleMetaContent);
172
+ }
173
+ res.json({ success: true, developerName });
174
+ }
175
+ catch (err) {
176
+ res.status(500).json({
177
+ error: err instanceof Error ? err.message : "Update agent failed",
178
+ });
179
+ }
180
+ });
181
+ }
@@ -0,0 +1,15 @@
1
+ import type { AgentInstance } from "@salesforce/agents";
2
+ import type { Connection as JSForceConnection } from "jsforce";
3
+ import type { Connection as SfConnection } from "@salesforce/core";
4
+ import { type OrgInfo } from "../org.js";
5
+ /** Session ID -> agent instance (for preview sendMessage, trace, endSession) */
6
+ export declare const sessionAgents: Map<string, AgentInstance>;
7
+ export interface ServerContext {
8
+ projectDir: string;
9
+ orgAlias?: string;
10
+ orgInfo?: OrgInfo;
11
+ jsforceConn?: JSForceConnection;
12
+ sfConn?: SfConnection;
13
+ }
14
+ /** Initialize org connection (jsforce + @salesforce/core). Call at server startup. */
15
+ export declare function initializeContext(context: ServerContext): Promise<void>;
@@ -0,0 +1,11 @@
1
+ import { getOrgInfoFromCli, createJsforceConnection, createSfConnection, } from "../org.js";
2
+ /** Session ID -> agent instance (for preview sendMessage, trace, endSession) */
3
+ export const sessionAgents = new Map();
4
+ /** Initialize org connection (jsforce + @salesforce/core). Call at server startup. */
5
+ export async function initializeContext(context) {
6
+ if (context.orgInfo)
7
+ return;
8
+ context.orgInfo = getOrgInfoFromCli(context.orgAlias, context.projectDir);
9
+ context.jsforceConn = createJsforceConnection(context.orgInfo);
10
+ context.sfConn = await createSfConnection(context.orgInfo);
11
+ }
@@ -0,0 +1,5 @@
1
+ import { Router } from "express";
2
+ import type { ServerContext } from "./context.js";
3
+ export type { ServerContext } from "./context.js";
4
+ export { initializeContext } from "./context.js";
5
+ export declare function createRoutes(context: ServerContext): Router;
@@ -0,0 +1,13 @@
1
+ import { Router } from "express";
2
+ import { registerSessionRoutes } from "./session.js";
3
+ import { registerAgentsRoutes } from "./agents.js";
4
+ import { registerTestsRoutes } from "./tests.js";
5
+ export { initializeContext } from "./context.js";
6
+ export function createRoutes(context) {
7
+ const router = Router();
8
+ // All routes on same router - avoids sub-router mounting issues
9
+ registerSessionRoutes(router, context);
10
+ registerAgentsRoutes(router, context);
11
+ registerTestsRoutes(router, context);
12
+ return router;
13
+ }
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { ServerContext } from "./context.js";
3
+ export declare function registerSessionRoutes(router: Router, context: ServerContext): void;
@@ -0,0 +1,195 @@
1
+ import { Agent } from "@salesforce/agents";
2
+ import { SfProject } from "@salesforce/core";
3
+ import { initializeContext } from "./context.js";
4
+ import { sessionAgents } from "./context.js";
5
+ /** Normalize API response to PlannerResponse. Handles AgentTraceResponse format. */
6
+ function normalizeTrace(raw) {
7
+ if (!raw || typeof raw !== "object")
8
+ return null;
9
+ const obj = raw;
10
+ // API may return { actions: [{ returnValue: { planId, sessionId, intent, topic, plan } }] }
11
+ const actions = obj.actions;
12
+ if (Array.isArray(actions) && actions.length > 0) {
13
+ const first = actions[0];
14
+ const rv = first?.returnValue;
15
+ if (rv && typeof rv.planId === "string") {
16
+ return {
17
+ type: "PlanSuccessResponse",
18
+ planId: rv.planId,
19
+ sessionId: rv.sessionId ?? "",
20
+ intent: rv.intent ?? "",
21
+ topic: rv.topic ?? "",
22
+ plan: Array.isArray(rv.plan) ? rv.plan : [],
23
+ };
24
+ }
25
+ }
26
+ // Already PlannerResponse format
27
+ if (typeof obj.planId === "string" && Array.isArray(obj.plan)) {
28
+ return obj;
29
+ }
30
+ return null;
31
+ }
32
+ export function registerSessionRoutes(router, context) {
33
+ async function ensureInitialized() {
34
+ if (!context.orgInfo) {
35
+ await initializeContext(context);
36
+ }
37
+ }
38
+ /**
39
+ * POST /startSession
40
+ * Body: { agentBundleName } for script agent OR { apiNameOrId } for production agent
41
+ * Optional: { useMock } - for script agents only. true = simulated (default), false = live Apex/flows.
42
+ */
43
+ router.post("/startSession", async (req, res) => {
44
+ try {
45
+ await ensureInitialized();
46
+ const agentBundleName = req.body.agentBundleName ?? req.body.aabName;
47
+ const apiNameOrId = req.body.apiNameOrId;
48
+ const useMock = req.body.useMock !== false; // default true (simulated)
49
+ if (!agentBundleName && !apiNameOrId) {
50
+ res.status(400).json({
51
+ error: "Missing required field: agentBundleName (script agent) or apiNameOrId (production agent)",
52
+ });
53
+ return;
54
+ }
55
+ const project = await SfProject.resolve(context.projectDir);
56
+ const agent = agentBundleName
57
+ ? await Agent.init({
58
+ connection: context.sfConn,
59
+ project,
60
+ aabName: agentBundleName,
61
+ })
62
+ : await Agent.init({
63
+ connection: context.sfConn,
64
+ project,
65
+ apiNameOrId: apiNameOrId,
66
+ });
67
+ const mockMode = agentBundleName ? (useMock ? "Mock" : "Live Test") : undefined;
68
+ const startResponse = await agent.preview.start(mockMode);
69
+ sessionAgents.set(startResponse.sessionId, agent);
70
+ res.json(startResponse);
71
+ }
72
+ catch (err) {
73
+ res.status(500).json({
74
+ error: err instanceof Error ? err.message : "Start session failed",
75
+ });
76
+ }
77
+ });
78
+ /**
79
+ * POST /sendmessage
80
+ * Body: { sessionId, message }
81
+ */
82
+ router.post("/sendmessage", async (req, res) => {
83
+ try {
84
+ const { sessionId, message } = req.body;
85
+ if (!sessionId || !message) {
86
+ res.status(400).json({
87
+ error: "Missing required fields: sessionId, message",
88
+ });
89
+ return;
90
+ }
91
+ const agent = sessionAgents.get(sessionId);
92
+ if (!agent) {
93
+ res.status(404).json({
94
+ error: "Session not found. Call startSession first.",
95
+ });
96
+ return;
97
+ }
98
+ const sendResponse = await agent.preview.send(message);
99
+ res.json(sendResponse);
100
+ }
101
+ catch (err) {
102
+ res.status(500).json({
103
+ error: err instanceof Error ? err.message : "Send message failed",
104
+ });
105
+ }
106
+ });
107
+ /**
108
+ * POST /trace
109
+ * Get the plan trace for a given planId from an active session.
110
+ * Body: { sessionId, planId }
111
+ */
112
+ router.post("/trace", async (req, res) => {
113
+ try {
114
+ const { sessionId, planId } = req.body;
115
+ if (!sessionId || !planId) {
116
+ res.status(400).json({
117
+ error: "Missing required fields: sessionId, planId",
118
+ });
119
+ return;
120
+ }
121
+ const agent = sessionAgents.get(sessionId);
122
+ if (!agent) {
123
+ res.status(404).json({
124
+ error: "Session not found. Call startSession first.",
125
+ });
126
+ return;
127
+ }
128
+ let rawTrace;
129
+ try {
130
+ rawTrace = await agent.getTrace(planId);
131
+ }
132
+ catch (apiErr) {
133
+ console.warn(`[trace] getTrace API failed for planId=${planId}:`, apiErr);
134
+ rawTrace = undefined;
135
+ }
136
+ let trace = null;
137
+ if (rawTrace !== undefined && rawTrace !== null) {
138
+ trace = normalizeTrace(rawTrace);
139
+ }
140
+ // Fallback: read from disk (script agents write traces during sendMessage)
141
+ if (!trace && agent.preview.getAllTraces) {
142
+ try {
143
+ const allTraces = await agent.preview.getAllTraces();
144
+ for (const t of allTraces) {
145
+ const normalized = normalizeTrace(t) ?? t;
146
+ if (normalized.planId === planId) {
147
+ trace = normalized;
148
+ break;
149
+ }
150
+ }
151
+ }
152
+ catch {
153
+ // getAllTraces may throw if historyDir doesn't exist
154
+ }
155
+ }
156
+ if (!trace) {
157
+ console.warn(`[trace] No trace for planId=${planId} (session=${sessionId}). Trace is only available for script agents (agentBundleName), not published agents (apiNameOrId).`);
158
+ }
159
+ res.json(trace);
160
+ }
161
+ catch (err) {
162
+ res.status(500).json({
163
+ error: err instanceof Error ? err.message : "Get trace failed",
164
+ });
165
+ }
166
+ });
167
+ /**
168
+ * POST /endSession
169
+ * Body: { sessionId }
170
+ */
171
+ router.post("/endSession", async (req, res) => {
172
+ try {
173
+ const { sessionId } = req.body;
174
+ if (!sessionId) {
175
+ res.status(400).json({ error: "Missing required field: sessionId" });
176
+ return;
177
+ }
178
+ const agent = sessionAgents.get(sessionId);
179
+ if (!agent) {
180
+ res.status(404).json({
181
+ error: "Session not found. Call startSession first.",
182
+ });
183
+ return;
184
+ }
185
+ const endResponse = await agent.preview.end();
186
+ sessionAgents.delete(sessionId);
187
+ res.json(endResponse);
188
+ }
189
+ catch (err) {
190
+ res.status(500).json({
191
+ error: err instanceof Error ? err.message : "End session failed",
192
+ });
193
+ }
194
+ });
195
+ }
@@ -0,0 +1,3 @@
1
+ import type { Router } from "express";
2
+ import type { ServerContext } from "./context.js";
3
+ export declare function registerTestsRoutes(router: Router, context: ServerContext): void;
@@ -0,0 +1,211 @@
1
+ import { file, directory } from "@polycuber/script.cli";
2
+ import { readdirSync } from "node:fs";
3
+ import { join } from "node:path";
4
+ import { Agent } from "@salesforce/agents";
5
+ import { SfProject } from "@salesforce/core";
6
+ import { initializeContext } from "./context.js";
7
+ import { sessionAgents } from "./context.js";
8
+ const API_VERSION = "v63.0";
9
+ export function registerTestsRoutes(router, context) {
10
+ async function ensureInitialized() {
11
+ if (!context.orgInfo) {
12
+ await initializeContext(context);
13
+ }
14
+ }
15
+ /**
16
+ * GET /tests/runs/:runId
17
+ * Must be registered before /tests/:filename so "runs" is not captured.
18
+ */
19
+ router.get("/tests/runs/:runId", async (req, res) => {
20
+ try {
21
+ await ensureInitialized();
22
+ const { runId } = req.params;
23
+ const includeResults = req.query.results === "true";
24
+ const path = includeResults
25
+ ? `/services/data/${API_VERSION}/einstein/ai-evaluations/runs/${runId}/results`
26
+ : `/services/data/${API_VERSION}/einstein/ai-evaluations/runs/${runId}`;
27
+ const result = await context.jsforceConn.request({
28
+ method: "GET",
29
+ url: path,
30
+ });
31
+ res.json(result);
32
+ }
33
+ catch (err) {
34
+ res.status(500).json({
35
+ error: err instanceof Error ? err.message : "Retrieve test run failed",
36
+ });
37
+ }
38
+ });
39
+ /**
40
+ * GET /tests
41
+ * Query: ?dir=root|example|all (default: all)
42
+ */
43
+ router.get("/tests", (req, res) => {
44
+ try {
45
+ const dirFilter = req.query.dir ?? "all";
46
+ const tests = [];
47
+ const dirsToScan = dirFilter === "example"
48
+ ? [join(context.projectDir, "example")]
49
+ : dirFilter === "root"
50
+ ? [context.projectDir]
51
+ : [context.projectDir, join(context.projectDir, "example")];
52
+ for (const dir of dirsToScan) {
53
+ if (!directory.exists(dir))
54
+ continue;
55
+ const entries = readdirSync(dir, { withFileTypes: true });
56
+ for (const e of entries) {
57
+ if (e.isFile() && e.name.endsWith(".json") && /test/i.test(e.name)) {
58
+ tests.push({
59
+ name: e.name,
60
+ path: join(dir, e.name),
61
+ });
62
+ }
63
+ }
64
+ }
65
+ res.json({ tests: tests.map((t) => ({ name: t.name, path: t.path })) });
66
+ }
67
+ catch (err) {
68
+ res.status(500).json({
69
+ error: err instanceof Error ? err.message : "List tests failed",
70
+ });
71
+ }
72
+ });
73
+ /**
74
+ * GET /tests/:filename
75
+ */
76
+ router.get("/tests/:filename", (req, res) => {
77
+ try {
78
+ const { filename } = req.params;
79
+ if (!filename || !/^[\w.-]+\.json$/i.test(filename)) {
80
+ res.status(400).json({ error: "Invalid filename" });
81
+ return;
82
+ }
83
+ const candidates = [
84
+ join(context.projectDir, filename),
85
+ join(context.projectDir, "example", filename),
86
+ ];
87
+ for (const p of candidates) {
88
+ if (file.exists(p)) {
89
+ const content = file.read.text(p);
90
+ const parsed = JSON.parse(content ?? "[]");
91
+ return res.json(parsed);
92
+ }
93
+ }
94
+ res.status(404).json({ error: `Test not found: ${filename}` });
95
+ }
96
+ catch (err) {
97
+ res.status(500).json({
98
+ error: err instanceof Error ? err.message : "Retrieve test failed",
99
+ });
100
+ }
101
+ });
102
+ /**
103
+ * POST /tests/run
104
+ * Body: { aiEvaluationDefinitionName } or { testFile, agentBundleName }
105
+ */
106
+ router.post("/tests/run", async (req, res) => {
107
+ try {
108
+ await ensureInitialized();
109
+ const { aiEvaluationDefinitionName, aiEvaluationDefinitionId, testFile, agentBundleName, } = req.body;
110
+ if (aiEvaluationDefinitionName || aiEvaluationDefinitionId) {
111
+ if (aiEvaluationDefinitionName && aiEvaluationDefinitionId) {
112
+ res.status(400).json({
113
+ error: "Provide exactly one: aiEvaluationDefinitionName or aiEvaluationDefinitionId",
114
+ });
115
+ return;
116
+ }
117
+ const body = aiEvaluationDefinitionName
118
+ ? { aiEvaluationDefinitionName }
119
+ : { aiEvaluationDefinitionId: aiEvaluationDefinitionId };
120
+ const result = (await context.jsforceConn.request({
121
+ method: "POST",
122
+ url: `/services/data/${API_VERSION}/einstein/ai-evaluations/runs`,
123
+ body: JSON.stringify(body),
124
+ }));
125
+ res.json({ runId: result.runId });
126
+ return;
127
+ }
128
+ if (testFile && agentBundleName) {
129
+ const candidates = [
130
+ join(context.projectDir, testFile),
131
+ join(context.projectDir, "example", testFile),
132
+ ];
133
+ let content;
134
+ for (const p of candidates) {
135
+ if (file.exists(p)) {
136
+ content = file.read.text(p);
137
+ break;
138
+ }
139
+ }
140
+ if (!content) {
141
+ res.status(404).json({ error: `Test file not found: ${testFile}` });
142
+ return;
143
+ }
144
+ const parsed = JSON.parse(content);
145
+ const cases = Array.isArray(parsed)
146
+ ? parsed
147
+ : parsed?.testSuite?.testCases ?? parsed?.testCases ?? [];
148
+ const flatCases = cases.flatMap((c) => {
149
+ const obj = c;
150
+ return Array.isArray(obj?.testCases) ? obj.testCases : [c];
151
+ });
152
+ const project = await SfProject.resolve(context.projectDir);
153
+ const agent = await Agent.init({
154
+ connection: context.sfConn,
155
+ project,
156
+ aabName: agentBundleName,
157
+ });
158
+ const startRes = await agent.preview.start();
159
+ const sessionId = startRes.sessionId;
160
+ sessionAgents.set(sessionId, agent);
161
+ const results = [];
162
+ try {
163
+ for (let i = 0; i < flatCases.length; i++) {
164
+ const tc = flatCases[i];
165
+ const utterance = tc["utterance"] ??
166
+ tc.utterance ??
167
+ tc.inputs?.utterance ??
168
+ (Array.isArray(tc.interactions)
169
+ ? tc.interactions
170
+ .map((a) => a.message)
171
+ .join(" ")
172
+ : "");
173
+ if (!utterance)
174
+ continue;
175
+ try {
176
+ await agent.preview.send(utterance);
177
+ results.push({ index: i + 1, utterance, passed: true });
178
+ }
179
+ catch (e) {
180
+ results.push({
181
+ index: i + 1,
182
+ utterance,
183
+ passed: false,
184
+ error: e instanceof Error ? e.message : String(e),
185
+ });
186
+ }
187
+ }
188
+ }
189
+ finally {
190
+ await agent.preview.end();
191
+ sessionAgents.delete(sessionId);
192
+ }
193
+ res.json({
194
+ total: flatCases.length,
195
+ passed: results.filter((r) => r.passed).length,
196
+ failed: results.filter((r) => !r.passed).length,
197
+ results,
198
+ });
199
+ return;
200
+ }
201
+ res.status(400).json({
202
+ error: "Provide aiEvaluationDefinitionName (or aiEvaluationDefinitionId) for SF run, or testFile+agentBundleName for local run",
203
+ });
204
+ }
205
+ catch (err) {
206
+ res.status(500).json({
207
+ error: err instanceof Error ? err.message : "Run test failed",
208
+ });
209
+ }
210
+ });
211
+ }
@@ -0,0 +1,14 @@
1
+ import { Router } from "express";
2
+ import type { Connection as JSForceConnection } from "jsforce";
3
+ import type { Connection as SfConnection } from "@salesforce/core";
4
+ import { type OrgInfo } from "./org.js";
5
+ export interface ServerContext {
6
+ projectDir: string;
7
+ orgAlias?: string;
8
+ orgInfo?: OrgInfo;
9
+ jsforceConn?: JSForceConnection;
10
+ sfConn?: SfConnection;
11
+ }
12
+ /** Initialize org connection (jsforce + @salesforce/core). Call at server startup. */
13
+ export declare function initializeContext(context: ServerContext): Promise<void>;
14
+ export declare function createRoutes(context: ServerContext): Router;