Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion example-servers/sveltekit/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion example-servers/sveltekit/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"type": "module",
"dependencies": {
"deep-chat": "^2.2.2",
"eventsource-parser": "^1.1.2"
"eventsource-parser": "^1.1.2",
"salesforce-agent-api-client": "^1.0.1"
}
}
27 changes: 27 additions & 0 deletions example-servers/sveltekit/src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -232,5 +232,32 @@
errorMessages={{displayServiceErrorMessages: true}}
/>
</div>
<h1 class="server-title">Server for Salesforce Agentforce</h1>
<a href="https://developer.salesforce.com/docs/einstein/genai/guide/agent-api.html" target="_blank" rel="noreferrer">
<img
class="server-title-icon"
src="https://raw.githubusercontent.com/OvidijusParsiunas/deep-chat/HEAD/website/static/img/salesforceLogo.png"
style="width: 40px; margin-bottom: -8px; margin-left: 8px"
alt={'Title icon'}
/>
</a>
<h3>Make sure to set the SALESFORCE_INSTANCE_URL, SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET and SALESFORCE_AGENT_ID environment variable in your server</h3>
<div class="components">
<div class="diagonal-line" style="background: #e3f2fd"></div>
<deep-chat
style="border-radius: 10px"
introMessage={{text: "Send a chat message through an example server to Salesforce Agent. You may configure an Agentforce agent in your Salesforce account."}}
connect={{url: '/api/salesforce/agentforce'}}
requestBodyLimits={{maxMessages: -1}}
errorMessages={{displayServiceErrorMessages: true}}
/>
<deep-chat
style="border-radius: 10px"
introMessage={{text: "Send a streamed chat message through an example server to Salesforce Agentforce. You may configure an Agentforce agent in your Salesforce account."}}
connect={{url: '/api/salesforce/agentforce-stream', stream: true}}
requestBodyLimits={{maxMessages: -1}}
errorMessages={{displayServiceErrorMessages: true}}
/>
</div>
{/if}
</main>
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { DeepChatTextRequestBody } from "../../../types/deepChatTextRequestBody";
import type { RequestHandler } from "@sveltejs/kit";
import AgentApiClient from "salesforce-agent-api-client";
import { writable } from 'svelte/store';

export const config = {
runtime: "edge",
// this is used to enable streaming
dynamic: 'force-dynamic'
};

// Make sure to set the SALESFORCE_INSTANCE_URL, SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET and SALESFORCE_AGENT_ID environment variable

interface StreamEvent {
data: string;
event: string;
}

const salesforceSession = writable<string | null>(null);
let sessionStore: string | null = null;
salesforceSession.subscribe(value => {
sessionStore = value;
});

export const POST: RequestHandler = async ({ request }) => {
// Load config from .env file
const config = {
instanceUrl: process.env.SALESFORCE_INSTANCE_URL || "",
clientId: process.env.SALESFORCE_CLIENT_ID || "",
clientSecret: process.env.SALESFORCE_CLIENT_SECRET || "",
agentId: process.env.SALESFORCE_AGENT_ID || "",
};

// Configure Agent API client
const client = new AgentApiClient(config);
const responseStream = new TransformStream();
const writer = responseStream.writable.getWriter();
const encoder = new TextEncoder();

try {
// Authenticate
await client.authenticate();

// Get the message from the request
const messageRequestBody = (await request.json()) as DeepChatTextRequestBody;
const message = messageRequestBody.messages[messageRequestBody.messages.length - 1].text;

// Validate that a message was provided in the request
if (!message) {
throw new Error("No message provided");
}

let sessionId: string;

// Session Management
if (sessionStore) {
sessionId = sessionStore;
} else {
sessionId = await client.createSession();
salesforceSession.set(sessionId);
}

// Stream event handler
function streamEventHandler({ data, event }: StreamEvent) {
const eventData = JSON.parse(data);
// console.log('Event:', event);

switch (event) {
case 'TEXT_CHUNK':
// Handle TEXT_CHUNK event
// Write the event data to the stream in Deep Chat format
console.log('TEXT_CHUNK:', eventData.message?.message);
writer.write(encoder.encode(`data: ${JSON.stringify({ text: eventData.message?.message || '' })}\n\n`));
break;
case 'END_OF_TURN':
// Handle END_OF_TURN event
writer.close();
break;
case 'INFORM':
// do nothing for INFORM event
break;
default:
console.log('Unknown event:', eventData);
}

}

// Stream disconnect handler
async function streamDisconnectHandler() {
if (!writer.closed) {
writer.close();
}
}

// Send the streaming message
client.sendStreamingMessage(
sessionId,
message,
[], // Empty array for variables/context
streamEventHandler,
streamDisconnectHandler
);

return new Response(responseStream.readable, {
headers: {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache, no-transform',
},
});

} catch (error) {
console.error("Salesforce Agent API Error:", error);
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";

// Write error to stream and close it
writer.write(encoder.encode(`data: ${JSON.stringify({ text: "Error: " + errorMessage })}\n\n`));
writer.close();

return new Response(responseStream.readable, {
headers: {
'Content-Type': 'text/event-stream',
'Connection': 'keep-alive',
'Cache-Control': 'no-cache, no-transform',
},
});
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import type { DeepChatTextRequestBody } from "../../../types/deepChatTextRequestBody";
import type { RequestHandler } from "@sveltejs/kit";
import AgentApiClient from "salesforce-agent-api-client";
import { writable } from 'svelte/store';

export const config = {
runtime: "edge",
};

// Make sure to set the SALESFORCE_INSTANCE_URL, SALESFORCE_CLIENT_ID, SALESFORCE_CLIENT_SECRET and SALESFORCE_AGENT_ID environment variable

// Create a writable store to manage Salesforce session state
// This store holds the session ID for maintaining conversation context
// Initial value is null, indicating no active session
const salesforceSession = writable<string | null>(null);
let sessionStore: string | null = null;
salesforceSession.subscribe(value => {
sessionStore = value;
});

export const POST: RequestHandler = async ({ request }) => {
// Load config from env
const config = {
instanceUrl: process.env.SALESFORCE_INSTANCE_URL || "",
clientId: process.env.SALESFORCE_CLIENT_ID || "",
clientSecret: process.env.SALESFORCE_CLIENT_SECRET || "",
agentId: process.env.SALESFORCE_AGENT_ID || "",
};

// Configure Agent API client
const client = new AgentApiClient(config);

try {
// Authenticate
await client.authenticate();

// Get the message from the request
const messageRequestBody = (await request.json()) as DeepChatTextRequestBody;
const message = messageRequestBody.messages[messageRequestBody.messages.length - 1].text;

// Validate that a message was provided in the request
if (!message) {
throw new Error("No message provided");
}

let sessionId: string;

// Session Management:
// We either reuse an existing session from the store or create a new one.
// This helps maintain conversation context and reduces unnecessary session creation.
if (sessionStore) {
// Reuse the existing session ID from the store
sessionId = sessionStore;
} else {
// If no session exists, create a new one and store it
sessionId = await client.createSession();
salesforceSession.set(sessionId);
}

// Send the message to Salesforce Agent API and wait for the synchronous response
// The empty array parameter is for additional context that might be needed in future
const syncResponse = await client.sendSyncMessage(sessionId, message, []);

// Format and return the agent's response
// If no message is received, provide a fallback response
return new Response(JSON.stringify({
text: syncResponse.messages[0].message || "No response received from the Salesforce agent",
}), {
headers: { "content-type": "application/json" },
});

} catch (error) {
// Log the full error for debugging purposes
console.error("Salesforce Agent API Error:", error);
// Extract error message safely, providing a fallback for non-Error objects
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred";
return new Response(
JSON.stringify({
text: "Error communicating with Salesforce Agent API: " + errorMessage,
}),
{
status: 500,
headers: {
"content-type": "application/json",
},
}
);
}
};
Binary file added website/static/img/salesforceLogo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.