@salesforce/sfdx-agent-chat-generations 0.0.1
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/CHANGELOG.md +10 -0
- package/LICENSE.txt +21 -0
- package/README.md +133 -0
- package/dist/chat-generations-language-model.d.ts +16 -0
- package/dist/chat-generations-language-model.js +402 -0
- package/dist/chat-generations-resolver.d.ts +72 -0
- package/dist/chat-generations-resolver.js +78 -0
- package/dist/chat-request.d.ts +117 -0
- package/dist/chat-request.js +14 -0
- package/dist/chat-response.d.ts +119 -0
- package/dist/chat-response.js +21 -0
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +18 -0
- package/dist/gpt5-response-processor.d.ts +6 -0
- package/dist/gpt5-response-processor.js +189 -0
- package/dist/index.d.ts +91 -0
- package/dist/index.js +52 -0
- package/dist/response-processor.d.ts +5 -0
- package/dist/response-processor.js +6 -0
- package/dist/sse-parser.d.ts +53 -0
- package/dist/sse-parser.js +116 -0
- package/dist/tool-arg-normalize.d.ts +1 -0
- package/dist/tool-arg-normalize.js +10 -0
- package/package.json +67 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Provider hint for the GovCloud `chat/generations` wire shape. Used to dispatch
|
|
7
|
+
* to the injected builder in the Mastra harness factory.
|
|
8
|
+
*/
|
|
9
|
+
export const CHAT_GENERATIONS_PROVIDER_HINT = 'salesforce-chat-generations';
|
|
10
|
+
/**
|
|
11
|
+
* Decorator over a consumer's real `AgentConnectivityResolver` that flips an existing
|
|
12
|
+
* connectivity bag onto the GovCloud `chat/generations` path. Reuses the delegate's
|
|
13
|
+
* `getHeaders()` verbatim (JWT rotation + tenant/feature headers for free), merging
|
|
14
|
+
* any GovCloud-required extras on top.
|
|
15
|
+
*
|
|
16
|
+
* **Why a decorator, not a subclass:** `DefaultAgentConnectivityResolver` and
|
|
17
|
+
* `buildSalesforceGatewayHeaders` are not on the SDK's public surface — subclassing
|
|
18
|
+
* would force new public exports. This decorator wraps the consumer's existing
|
|
19
|
+
* resolver (e.g., AFV's `VibesAgentConnectivityResolver`) and reuses its `getHeaders()`
|
|
20
|
+
* without needing SDK-internal header helpers.
|
|
21
|
+
*
|
|
22
|
+
* **Gating is explicit, not auto-detected.** Do not sniff `instanceUrl` for `.gov` —
|
|
23
|
+
* domain research showed that's unreliable (org instanceUrl differs from gateway host,
|
|
24
|
+
* and civilian gov is `*.salesforce.com`, indistinguishable from commercial). The
|
|
25
|
+
* consumer knows they're deploying to GovCloud; they wire this decorator at construction.
|
|
26
|
+
*
|
|
27
|
+
* **Consumer must set a GPT `modelId`.** The SDK default (Claude Sonnet 4.6) will not
|
|
28
|
+
* resolve in GovCloud. AFV must bind `AgentConfig.modelId` to a GovCloud-provisioned
|
|
29
|
+
* `sfdc_ai__DefaultGPT*` model id.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```ts
|
|
33
|
+
* const resolver = new ChatGenerationsResolver({
|
|
34
|
+
* delegate: myExistingResolver,
|
|
35
|
+
* baseUrl: 'https://dev.api.gov.salesforce.com/ai/gpt/v1',
|
|
36
|
+
* extraHeaders: { 'x-salesforce-region': 'us-gov-east-1' },
|
|
37
|
+
* });
|
|
38
|
+
* const manager = await createAgentManager(storageRoot, factory, { connectivityResolver: resolver });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export class ChatGenerationsResolver {
|
|
42
|
+
opts;
|
|
43
|
+
constructor(opts) {
|
|
44
|
+
this.opts = opts;
|
|
45
|
+
}
|
|
46
|
+
async resolve(projectRoot, config) {
|
|
47
|
+
// Delegate to the wrapped resolver to get the base connectivity bag
|
|
48
|
+
const inner = await this.opts.delegate.resolve(projectRoot, config);
|
|
49
|
+
// Kill-switch: when disabled, pass through the delegate's result unchanged
|
|
50
|
+
const enabled = this.opts.enabled ?? process.env['SF_GOVCLOUD_LEGACY_FALLBACK'] === '1';
|
|
51
|
+
if (!enabled) {
|
|
52
|
+
return inner;
|
|
53
|
+
}
|
|
54
|
+
const innerInfo = inner.modelConnectivityInfo;
|
|
55
|
+
// Rewrite the connectivity bag: flip `baseUrl` and `providerHint` onto the
|
|
56
|
+
// GovCloud `chat/generations` path, but leave `model` and `nativeModelId`
|
|
57
|
+
// untouched (the delegate already resolved the consumer's GPT `modelId` to
|
|
58
|
+
// the `sfdc_ai__DefaultGPT*` wire id the builder will send in the body).
|
|
59
|
+
return {
|
|
60
|
+
...inner,
|
|
61
|
+
modelConnectivityInfo: {
|
|
62
|
+
...innerInfo,
|
|
63
|
+
baseUrl: this.opts.baseUrl,
|
|
64
|
+
providerHint: CHAT_GENERATIONS_PROVIDER_HINT,
|
|
65
|
+
// Reuse the delegate's getHeaders VERBATIM. This closure re-invokes
|
|
66
|
+
// `innerInfo.getHeaders()` on every call (per-call freshness contract),
|
|
67
|
+
// then merges GovCloud-required extras on top (extras win on conflict).
|
|
68
|
+
// Do NOT snapshot the header map at resolve time — JWT rotation and
|
|
69
|
+
// feature-id swaps require re-evaluation per request.
|
|
70
|
+
getHeaders: async () => {
|
|
71
|
+
const delegateHeaders = await innerInfo.getHeaders();
|
|
72
|
+
return { ...delegateHeaders, ...(this.opts.extraHeaders ?? {}) };
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=chat-generations-resolver.js.map
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export type ChatRequest = {
|
|
2
|
+
messages: ChatMessageIn[];
|
|
3
|
+
generation_settings: GenerationSettings;
|
|
4
|
+
reasoning_settings?: ReasoningSettings;
|
|
5
|
+
tools?: ChatCompletionFunctionTool[];
|
|
6
|
+
tool_config?: ToolConfig;
|
|
7
|
+
};
|
|
8
|
+
type ReasoningSettings = {
|
|
9
|
+
/**
|
|
10
|
+
* Indicates whether to include thinking/reasoning content in the response. Defaults to `false`.
|
|
11
|
+
*/
|
|
12
|
+
return_reasoning?: boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Parameter that guides the model on the budget to use for reasoning tokens to generate before creating a response to the prompt.
|
|
15
|
+
*
|
|
16
|
+
* Only supported in OpenAI and Gemini models.
|
|
17
|
+
* See: https://git.soma.salesforce.com/pages/tech-enablement/einstein/docs/gateway/reasoning/
|
|
18
|
+
*/
|
|
19
|
+
reasoning_budget?: ReasoningBudget;
|
|
20
|
+
};
|
|
21
|
+
type ReasoningBudget = {
|
|
22
|
+
effort: 'minimal' | 'low' | 'medium' | 'high';
|
|
23
|
+
} | {
|
|
24
|
+
tokens: number;
|
|
25
|
+
};
|
|
26
|
+
export type ChatMessageIn = {
|
|
27
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
28
|
+
content: string;
|
|
29
|
+
files?: ChatMessageFile[];
|
|
30
|
+
tool_call_id?: string;
|
|
31
|
+
tool_call_name?: string;
|
|
32
|
+
tool_invocations?: ToolInvocationIn[];
|
|
33
|
+
};
|
|
34
|
+
/**
|
|
35
|
+
* MIME types accepted on the multimodal path.
|
|
36
|
+
* Any other value triggers a `LLMGClientError('MODEL_DOES_NOT_SUPPORT_FORMAT')` error.
|
|
37
|
+
*/
|
|
38
|
+
export declare const MimeType: {
|
|
39
|
+
readonly Png: "image/png";
|
|
40
|
+
readonly Jpeg: "image/jpeg";
|
|
41
|
+
readonly Pdf: "application/pdf";
|
|
42
|
+
};
|
|
43
|
+
export type MimeType = (typeof MimeType)[keyof typeof MimeType];
|
|
44
|
+
/**
|
|
45
|
+
* One image or PDF attached to a `ChatMessageIn`.
|
|
46
|
+
* v1 only supports `dataType: 'base64'`
|
|
47
|
+
* Allowed `mimeType` values are PNG, JPEG, and PDF — see {@link MimeType}.
|
|
48
|
+
*/
|
|
49
|
+
export type ChatMessageFile = {
|
|
50
|
+
/** Caller-supplied unique identifier (UUID recommended). */
|
|
51
|
+
fileId: string;
|
|
52
|
+
/** MIME type. Only PNG, JPEG, and PDF are accepted by the SDK in v1. */
|
|
53
|
+
mimeType: MimeType;
|
|
54
|
+
/** Transport. Only `'base64'` is supported in v1. */
|
|
55
|
+
dataType: 'base64';
|
|
56
|
+
/** Base64-encoded file bytes (no `data:` URI prefix). */
|
|
57
|
+
data: string;
|
|
58
|
+
/** Optional human-readable filename. Surfaces to providers that display it (e.g. Data Cloud). */
|
|
59
|
+
fileName?: string;
|
|
60
|
+
};
|
|
61
|
+
export type ToolInvocationIn = {
|
|
62
|
+
id: string;
|
|
63
|
+
function: {
|
|
64
|
+
name: string;
|
|
65
|
+
arguments: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
export type GenerationSettings = {
|
|
69
|
+
max_tokens?: number;
|
|
70
|
+
temperature?: number;
|
|
71
|
+
/**
|
|
72
|
+
* An array of stop sequences to be used. The generated text is cut at the end of the earliest occurrence of a stop sequence.
|
|
73
|
+
*/
|
|
74
|
+
stop_sequences?: string[];
|
|
75
|
+
/**
|
|
76
|
+
* The frequency penalty to be used. Min value is 0.0; max value is 1.0. Can reduce the repetitiveness of generated tokens. The higher the value, the stronger a penalty is applied to previously present tokens, proportional to how many times they have already appeared in the prompt or prior generation.
|
|
77
|
+
*/
|
|
78
|
+
frequency_penalty?: number;
|
|
79
|
+
/**
|
|
80
|
+
* The presence penalty to be used. Min value is 0.0; max value is 1.0. Can reduce the repetitiveness of generated tokens. Behaves similarly to frequency_penalty, except that this penalty is applied equally to all tokens that have already appeared, regardless of their exact frequencies.
|
|
81
|
+
*/
|
|
82
|
+
presence_penalty?: number;
|
|
83
|
+
/**
|
|
84
|
+
* Dictionary of any other parameters that are required by the specified provider. Values are passed as is to the provider so that the request can include parameters that are unique to a provider.
|
|
85
|
+
*/
|
|
86
|
+
parameters?: object;
|
|
87
|
+
};
|
|
88
|
+
export type ChatCompletionFunctionTool = {
|
|
89
|
+
type?: 'function';
|
|
90
|
+
function: {
|
|
91
|
+
name: string;
|
|
92
|
+
description?: string;
|
|
93
|
+
parameters?: {
|
|
94
|
+
type: 'object';
|
|
95
|
+
properties?: Record<string, LlmgPropertyDefinition>;
|
|
96
|
+
required?: string[];
|
|
97
|
+
};
|
|
98
|
+
strict?: boolean;
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
export type LlmgPropertyDefinition = ToolPropertyDefinition & {
|
|
102
|
+
enum?: string[];
|
|
103
|
+
items?: LlmgPropertyDefinition;
|
|
104
|
+
};
|
|
105
|
+
type ToolPropertyDefinition = {
|
|
106
|
+
type: string;
|
|
107
|
+
description?: string;
|
|
108
|
+
properties?: Record<string, ToolPropertyDefinition>;
|
|
109
|
+
};
|
|
110
|
+
export type ToolConfig = {
|
|
111
|
+
mode: 'auto' | 'none' | 'tool' | 'any';
|
|
112
|
+
allowed_tools?: {
|
|
113
|
+
type: 'function';
|
|
114
|
+
name: string;
|
|
115
|
+
}[];
|
|
116
|
+
};
|
|
117
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* MIME types accepted on the multimodal path.
|
|
7
|
+
* Any other value triggers a `LLMGClientError('MODEL_DOES_NOT_SUPPORT_FORMAT')` error.
|
|
8
|
+
*/
|
|
9
|
+
export const MimeType = {
|
|
10
|
+
Png: 'image/png',
|
|
11
|
+
Jpeg: 'image/jpeg',
|
|
12
|
+
Pdf: 'application/pdf',
|
|
13
|
+
};
|
|
14
|
+
//# sourceMappingURL=chat-request.js.map
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
type Response<T> = {
|
|
2
|
+
status: number;
|
|
3
|
+
data: T;
|
|
4
|
+
};
|
|
5
|
+
export type ChatResponse = Response<ChatResponseData>;
|
|
6
|
+
/**
|
|
7
|
+
* Union of all known finish reasons across supported models.
|
|
8
|
+
* GPT-5 uses: 'stop', 'length', 'tool_calls'
|
|
9
|
+
*/
|
|
10
|
+
export type FinishReason = 'stop' | 'length' | 'tool_calls';
|
|
11
|
+
export type ChatResponseData = {
|
|
12
|
+
generatedText?: string;
|
|
13
|
+
finishReason?: FinishReason;
|
|
14
|
+
error?: LLMGError;
|
|
15
|
+
toolInvocations?: ToolInvocation[];
|
|
16
|
+
usage?: TokenUsage;
|
|
17
|
+
};
|
|
18
|
+
export type ChatStreamResponse = Response<AsyncGenerator<ChatStreamChunk>>;
|
|
19
|
+
export type TokenUsage = {
|
|
20
|
+
inputTokens: number;
|
|
21
|
+
outputTokens: number;
|
|
22
|
+
totalTokens: number;
|
|
23
|
+
reasoningTokens?: number;
|
|
24
|
+
cacheReadTokens?: number;
|
|
25
|
+
cacheWriteTokens?: number;
|
|
26
|
+
};
|
|
27
|
+
export type ChatStreamChunk = {
|
|
28
|
+
generatedText: string;
|
|
29
|
+
done: boolean;
|
|
30
|
+
finishReason?: FinishReason;
|
|
31
|
+
error?: LLMGError;
|
|
32
|
+
toolInvocations?: ToolInvocation[];
|
|
33
|
+
usage?: TokenUsage;
|
|
34
|
+
};
|
|
35
|
+
export type ToolInvocation = {
|
|
36
|
+
id: string;
|
|
37
|
+
function: {
|
|
38
|
+
name: string;
|
|
39
|
+
arguments: string;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
export type RawToolInvocation = {
|
|
43
|
+
id: string;
|
|
44
|
+
function: {
|
|
45
|
+
name: string;
|
|
46
|
+
arguments: string;
|
|
47
|
+
};
|
|
48
|
+
thought_signature?: string | null;
|
|
49
|
+
};
|
|
50
|
+
export type LLMGErrorParameter = {
|
|
51
|
+
name: string;
|
|
52
|
+
value: unknown;
|
|
53
|
+
};
|
|
54
|
+
export type LLMGExtendedErrorDetails = {
|
|
55
|
+
providerStatusCode?: number;
|
|
56
|
+
[key: string]: unknown;
|
|
57
|
+
};
|
|
58
|
+
export type LLMGError = {
|
|
59
|
+
/**
|
|
60
|
+
* See: https://git.soma.salesforce.com/pages/tech-enablement/einstein/docs/gateway/error-codes/
|
|
61
|
+
*/
|
|
62
|
+
messageCode: string;
|
|
63
|
+
errorCode: string;
|
|
64
|
+
message: string;
|
|
65
|
+
extendedErrorDetails?: LLMGExtendedErrorDetails[];
|
|
66
|
+
parameters?: LLMGErrorParameter[];
|
|
67
|
+
};
|
|
68
|
+
type GPT5TokenUsageDetails = {
|
|
69
|
+
prompt_tokens: number;
|
|
70
|
+
completion_tokens: number;
|
|
71
|
+
total_tokens: number;
|
|
72
|
+
completion_tokens_details?: {
|
|
73
|
+
reasoning_tokens?: number;
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
type GPT5Generation = {
|
|
77
|
+
id?: string;
|
|
78
|
+
role: 'user' | 'assistant' | 'system' | 'tool';
|
|
79
|
+
content: string;
|
|
80
|
+
thought_signature?: string | null;
|
|
81
|
+
contents?: Array<{
|
|
82
|
+
type: 'thinking_summary' | 'redacted_thinking' | 'text';
|
|
83
|
+
content: string;
|
|
84
|
+
}> | null;
|
|
85
|
+
timestamp?: number;
|
|
86
|
+
parameters?: {
|
|
87
|
+
finish_reason?: 'stop' | 'length' | 'tool_calls' | null;
|
|
88
|
+
refusal?: string | null;
|
|
89
|
+
index?: number;
|
|
90
|
+
};
|
|
91
|
+
tool_invocations?: RawToolInvocation[] | null;
|
|
92
|
+
};
|
|
93
|
+
type GPT5GenerationParameters = {
|
|
94
|
+
provider: 'openai';
|
|
95
|
+
created?: number;
|
|
96
|
+
model?: string;
|
|
97
|
+
usage?: GPT5TokenUsageDetails;
|
|
98
|
+
};
|
|
99
|
+
type GPT5RawChatChunk = {
|
|
100
|
+
id: string;
|
|
101
|
+
generation_details: {
|
|
102
|
+
generations: GPT5Generation[];
|
|
103
|
+
parameters: GPT5GenerationParameters;
|
|
104
|
+
};
|
|
105
|
+
other_details?: unknown | null;
|
|
106
|
+
};
|
|
107
|
+
export type LLMGErrorEvent = {
|
|
108
|
+
error: LLMGError;
|
|
109
|
+
};
|
|
110
|
+
export type RawChatChunk = GPT5RawChatChunk | LLMGErrorEvent;
|
|
111
|
+
/**
|
|
112
|
+
* Type guard to check if a raw chunk is an error event.
|
|
113
|
+
*/
|
|
114
|
+
export declare function isLLMGErrorEvent(chunk: RawChatChunk): chunk is LLMGErrorEvent;
|
|
115
|
+
/**
|
|
116
|
+
* Type guard to check if a raw response is from GPT-5.
|
|
117
|
+
*/
|
|
118
|
+
export declare function isGPT5Response(response: RawChatChunk): response is GPT5RawChatChunk;
|
|
119
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
// =====================================================================================================================
|
|
6
|
+
// ==== TYPE GUARDS
|
|
7
|
+
// =====================================================================================================================
|
|
8
|
+
/**
|
|
9
|
+
* Type guard to check if a raw chunk is an error event.
|
|
10
|
+
*/
|
|
11
|
+
export function isLLMGErrorEvent(chunk) {
|
|
12
|
+
return 'error' in chunk && typeof chunk.error === 'object' && 'errorCode' in chunk.error;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Type guard to check if a raw response is from GPT-5.
|
|
16
|
+
*/
|
|
17
|
+
export function isGPT5Response(response) {
|
|
18
|
+
const params = 'generation_details' in response ? response.generation_details?.parameters : undefined;
|
|
19
|
+
return params !== undefined && 'provider' in params && params.provider === 'openai';
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=chat-response.js.map
|
package/dist/errors.d.ts
ADDED
package/dist/errors.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
// Ported from 3f694898^:packages/llm-gateway-sdk/src/errors.ts (W-23560954).
|
|
6
|
+
// Minimal subset: the LLMGError shape lives in chat-response.ts; this file is currently
|
|
7
|
+
// a placeholder for future error-handling needs.
|
|
8
|
+
export class LLMGClientError extends Error {
|
|
9
|
+
code;
|
|
10
|
+
httpStatus;
|
|
11
|
+
constructor(message, code, options) {
|
|
12
|
+
super(message, options);
|
|
13
|
+
this.name = 'LLMGatewayClientError';
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.httpStatus = options?.httpStatus;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ChatResponseData, ChatStreamChunk, RawChatChunk } from './chat-response.js';
|
|
2
|
+
import type { ResponseProcessor } from './response-processor.js';
|
|
3
|
+
export declare class GPT5ResponseProcessor implements ResponseProcessor {
|
|
4
|
+
processRawChatStream(rawStream: AsyncGenerator<RawChatChunk>): AsyncGenerator<ChatStreamChunk>;
|
|
5
|
+
processRawChatResponse(raw: RawChatChunk): ChatResponseData;
|
|
6
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright 2026, Salesforce, Inc. All rights reserved.
|
|
3
|
+
* See LICENSE.txt for license terms.
|
|
4
|
+
*/
|
|
5
|
+
import { isLLMGErrorEvent, isGPT5Response } from './chat-response.js';
|
|
6
|
+
import { normalizeToolArguments } from './tool-arg-normalize.js';
|
|
7
|
+
// Detect if this is the start of a new tool call (has id and name)
|
|
8
|
+
function isToolCallStart(inv) {
|
|
9
|
+
return !!(inv.id && inv.function?.name);
|
|
10
|
+
}
|
|
11
|
+
// Convert accumulated tool call to normalized ToolInvocation
|
|
12
|
+
function toToolInvocation(pending) {
|
|
13
|
+
return {
|
|
14
|
+
id: pending.id,
|
|
15
|
+
function: {
|
|
16
|
+
name: pending.name,
|
|
17
|
+
arguments: normalizeToolArguments(pending.argumentsBuffer),
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export class GPT5ResponseProcessor {
|
|
22
|
+
async *processRawChatStream(rawStream) {
|
|
23
|
+
let pendingToolCall = null;
|
|
24
|
+
let capturedUsage;
|
|
25
|
+
let bufferedFinalChunk = null;
|
|
26
|
+
for await (const raw of rawStream) {
|
|
27
|
+
// Check for error events first
|
|
28
|
+
if (isLLMGErrorEvent(raw)) {
|
|
29
|
+
yield {
|
|
30
|
+
generatedText: '',
|
|
31
|
+
done: true,
|
|
32
|
+
error: raw.error,
|
|
33
|
+
};
|
|
34
|
+
return; // Error events are terminal - stop processing
|
|
35
|
+
}
|
|
36
|
+
// Type guard ensures we have GPT5RawChatChunk from here on
|
|
37
|
+
if (!isGPT5Response(raw)) {
|
|
38
|
+
// This should never happen in practice, but we need to handle it for type safety
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
const generation = raw.generation_details?.generations?.[0];
|
|
42
|
+
// Check for usage chunk (empty generations with usage in parameters)
|
|
43
|
+
// Usage info comes in the final chunk.
|
|
44
|
+
//
|
|
45
|
+
// See: https://platform.openai.com/docs/api-reference/completions/object#completions-object-usage
|
|
46
|
+
if (!generation) {
|
|
47
|
+
const usage = raw.generation_details?.parameters.usage;
|
|
48
|
+
if (usage) {
|
|
49
|
+
const inputTokens = usage.prompt_tokens ?? 0;
|
|
50
|
+
const outputTokens = usage.completion_tokens ?? 0;
|
|
51
|
+
const reasoningTokens = usage.completion_tokens_details?.reasoning_tokens;
|
|
52
|
+
capturedUsage = {
|
|
53
|
+
inputTokens,
|
|
54
|
+
outputTokens,
|
|
55
|
+
totalTokens: usage.total_tokens,
|
|
56
|
+
...(reasoningTokens ? { reasoningTokens } : {}),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
const finishReason = generation.parameters?.finish_reason;
|
|
62
|
+
const isDone = !!finishReason;
|
|
63
|
+
const content = generation.content || '';
|
|
64
|
+
const toolInvocation = generation.tool_invocations?.[0];
|
|
65
|
+
// NOTE (cristian):
|
|
66
|
+
// I haven't found any LLMG/OpenAI doc about this being a possible case,
|
|
67
|
+
// we are handling it just to be safe.
|
|
68
|
+
//
|
|
69
|
+
// If chunk has BOTH content and tool invocations, yield text first
|
|
70
|
+
if (content && toolInvocation) {
|
|
71
|
+
yield {
|
|
72
|
+
generatedText: content,
|
|
73
|
+
done: false,
|
|
74
|
+
};
|
|
75
|
+
// Continue to process tool invocation below
|
|
76
|
+
}
|
|
77
|
+
// Check for tool invocations
|
|
78
|
+
if (toolInvocation) {
|
|
79
|
+
if (isToolCallStart(toolInvocation)) {
|
|
80
|
+
// Flush any pending tool call first
|
|
81
|
+
if (pendingToolCall) {
|
|
82
|
+
yield {
|
|
83
|
+
generatedText: '',
|
|
84
|
+
done: false,
|
|
85
|
+
toolInvocations: [toToolInvocation(pendingToolCall)],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Start accumulating new tool call
|
|
89
|
+
pendingToolCall = {
|
|
90
|
+
id: toolInvocation.id,
|
|
91
|
+
name: toolInvocation.function.name,
|
|
92
|
+
argumentsBuffer: toolInvocation.function.arguments || '',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
else if (pendingToolCall) {
|
|
96
|
+
// Continuation - append arguments
|
|
97
|
+
pendingToolCall.argumentsBuffer += toolInvocation.function.arguments || '';
|
|
98
|
+
}
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
// Handle done signal - may come in a separate chunk without tool_invocations
|
|
102
|
+
// (e.g., finish_reason: "tool_calls" arrives after all tool fragments)
|
|
103
|
+
if (isDone && pendingToolCall) {
|
|
104
|
+
bufferedFinalChunk = {
|
|
105
|
+
generatedText: '',
|
|
106
|
+
done: true,
|
|
107
|
+
finishReason: finishReason ?? undefined,
|
|
108
|
+
toolInvocations: [toToolInvocation(pendingToolCall)],
|
|
109
|
+
};
|
|
110
|
+
pendingToolCall = null;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// Text content - yield immediately for low latency
|
|
114
|
+
// Note: If text content + toolInvocation was present, we already continued above,
|
|
115
|
+
// so this only handles text-only chunks or done signals without tools.
|
|
116
|
+
if (content || isDone) {
|
|
117
|
+
// For text content with done signal, buffer to capture usage later
|
|
118
|
+
if (isDone) {
|
|
119
|
+
bufferedFinalChunk = {
|
|
120
|
+
generatedText: content,
|
|
121
|
+
done: true,
|
|
122
|
+
finishReason: finishReason ?? undefined,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
else if (content) {
|
|
126
|
+
yield {
|
|
127
|
+
generatedText: content,
|
|
128
|
+
done: false,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// Stream ended - yield buffered final chunk WITHOUT usage, then usage chunk separately
|
|
134
|
+
if (bufferedFinalChunk) {
|
|
135
|
+
// Yield the content chunk first with done=false
|
|
136
|
+
yield {
|
|
137
|
+
...bufferedFinalChunk,
|
|
138
|
+
done: false, // NOT the final chunk - usage chunk comes next
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
else if (pendingToolCall) {
|
|
142
|
+
// Stream ended with pending tool call (no finish_reason received)
|
|
143
|
+
yield {
|
|
144
|
+
generatedText: '',
|
|
145
|
+
done: false,
|
|
146
|
+
toolInvocations: [toToolInvocation(pendingToolCall)],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// ALWAYS yield a final usage chunk with done=true (even if usage is undefined)
|
|
150
|
+
// This ensures there's always exactly ONE chunk with done=true at the end
|
|
151
|
+
yield {
|
|
152
|
+
generatedText: '',
|
|
153
|
+
done: true, // This is the FINAL chunk
|
|
154
|
+
finishReason: bufferedFinalChunk?.finishReason,
|
|
155
|
+
usage: capturedUsage, // May be undefined if API didn't send usage
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
processRawChatResponse(raw) {
|
|
159
|
+
if (isLLMGErrorEvent(raw)) {
|
|
160
|
+
throw new Error(raw.error.message);
|
|
161
|
+
}
|
|
162
|
+
if (!isGPT5Response(raw)) {
|
|
163
|
+
throw new Error('Invalid response type for GPT-5 processor');
|
|
164
|
+
}
|
|
165
|
+
const generation = raw.generation_details?.generations?.[0];
|
|
166
|
+
const generatedText = generation?.content ?? '';
|
|
167
|
+
const finishReason = generation?.parameters?.finish_reason ?? undefined;
|
|
168
|
+
const toolInvocations = (generation?.tool_invocations ?? []).map((inv) => ({
|
|
169
|
+
id: inv.id,
|
|
170
|
+
function: {
|
|
171
|
+
name: inv.function.name,
|
|
172
|
+
arguments: normalizeToolArguments(inv.function.arguments),
|
|
173
|
+
},
|
|
174
|
+
}));
|
|
175
|
+
let usage;
|
|
176
|
+
const rawUsage = raw.generation_details?.parameters.usage;
|
|
177
|
+
if (rawUsage) {
|
|
178
|
+
const reasoningTokens = rawUsage.completion_tokens_details?.reasoning_tokens;
|
|
179
|
+
usage = {
|
|
180
|
+
inputTokens: rawUsage.prompt_tokens ?? 0,
|
|
181
|
+
outputTokens: rawUsage.completion_tokens ?? 0,
|
|
182
|
+
totalTokens: rawUsage.total_tokens,
|
|
183
|
+
...(reasoningTokens ? { reasoningTokens } : {}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
return { generatedText, finishReason, toolInvocations, usage };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=gpt5-response-processor.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
export { buildChatGenerationsLanguageModel } from './chat-generations-language-model.js';
|
|
2
|
+
export { ChatGenerationsResolver, CHAT_GENERATIONS_PROVIDER_HINT } from './chat-generations-resolver.js';
|
|
3
|
+
export type { ChatGenerationsResolverOptions } from './chat-generations-resolver.js';
|
|
4
|
+
import type { MastraLanguageModelBuilder } from '@salesforce/sfdx-agent-harness-mastra';
|
|
5
|
+
import { ChatGenerationsResolver, CHAT_GENERATIONS_PROVIDER_HINT, type ChatGenerationsResolverOptions } from './chat-generations-resolver.js';
|
|
6
|
+
/**
|
|
7
|
+
* The return value of {@link createChatGenerationsFallback}, carrying a connectivity
|
|
8
|
+
* resolver decorator and a language-model builder ready to spread into
|
|
9
|
+
* `createAgentManager` and `MastraHarnessFactory`.
|
|
10
|
+
*
|
|
11
|
+
* @remarks
|
|
12
|
+
* Consumers wire the fallback at construction time, either explicitly (construct
|
|
13
|
+
* `ChatGenerationsResolver` and pass `languageModelBuilders` separately) or via the
|
|
14
|
+
* convenience factory (destructure its return and spread both fields). Both forms produce
|
|
15
|
+
* identical runtime wiring.
|
|
16
|
+
*
|
|
17
|
+
* @example Explicit construction
|
|
18
|
+
* ```ts
|
|
19
|
+
* const factory = new MastraHarnessFactory({
|
|
20
|
+
* languageModelBuilders: {
|
|
21
|
+
* [CHAT_GENERATIONS_PROVIDER_HINT]: buildChatGenerationsLanguageModel,
|
|
22
|
+
* },
|
|
23
|
+
* });
|
|
24
|
+
* const resolver = new ChatGenerationsResolver({
|
|
25
|
+
* delegate: myExistingResolver,
|
|
26
|
+
* baseUrl: 'https://dev.api.gov.salesforce.com/ai/gpt/v1',
|
|
27
|
+
* extraHeaders: { 'x-salesforce-region': 'us-gov-east-1' },
|
|
28
|
+
* });
|
|
29
|
+
* const manager = await createAgentManager(storageRoot, factory, { connectivityResolver: resolver });
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example Convenience factory (preferred)
|
|
33
|
+
* ```ts
|
|
34
|
+
* const { connectivityResolver, languageModelBuilders } = createChatGenerationsFallback({
|
|
35
|
+
* delegate: myExistingResolver,
|
|
36
|
+
* baseUrl: 'https://dev.api.gov.salesforce.com/ai/gpt/v1',
|
|
37
|
+
* extraHeaders: { 'x-salesforce-region': 'us-gov-east-1' },
|
|
38
|
+
* });
|
|
39
|
+
* const factory = new MastraHarnessFactory({ languageModelBuilders });
|
|
40
|
+
* const manager = await createAgentManager(storageRoot, factory, { connectivityResolver });
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export type ChatGenerationsFallback = {
|
|
44
|
+
/**
|
|
45
|
+
* The connectivity resolver decorator wrapping the consumer's delegate. Passes to
|
|
46
|
+
* `createAgentManager`'s `options.connectivityResolver` parameter.
|
|
47
|
+
*/
|
|
48
|
+
connectivityResolver: ChatGenerationsResolver;
|
|
49
|
+
/**
|
|
50
|
+
* Language-model builder map keyed by `CHAT_GENERATIONS_PROVIDER_HINT`, ready to spread
|
|
51
|
+
* into `MastraHarnessFactory`'s `languageModelBuilders` constructor option. The value is
|
|
52
|
+
* typed explicitly as `MastraLanguageModelBuilder` so TS assignability checks succeed
|
|
53
|
+
* without widening.
|
|
54
|
+
*/
|
|
55
|
+
languageModelBuilders: Record<typeof CHAT_GENERATIONS_PROVIDER_HINT, MastraLanguageModelBuilder>;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Convenience factory for constructing the GovCloud `chat/generations` fallback wiring.
|
|
59
|
+
* Returns a `{ connectivityResolver, languageModelBuilders }` object ready to spread into
|
|
60
|
+
* `createAgentManager` and `MastraHarnessFactory` constructors (W-23560954).
|
|
61
|
+
*
|
|
62
|
+
* @remarks
|
|
63
|
+
* This fallback routes all model traffic through the legacy GovCloud `chat/generations`
|
|
64
|
+
* wire shape. The consumer MUST set `AgentConfig.modelId` to a GovCloud-provisioned GPT
|
|
65
|
+
* model (e.g., `sfdc_ai__DefaultGPT4_8`) — the SDK default (Claude Sonnet 4.6) will not
|
|
66
|
+
* resolve in GovCloud. The resolver decorator flips the delegate's `baseUrl` and
|
|
67
|
+
* `providerHint` onto the GovCloud path, and the injected language-model builder speaks
|
|
68
|
+
* the Salesforce-proprietary `chat/generations` wire contract.
|
|
69
|
+
*
|
|
70
|
+
* **This package is temporary** and will be deleted once the Responses API is onboarded
|
|
71
|
+
* in GovCloud (expected within a few weeks).
|
|
72
|
+
*
|
|
73
|
+
* @param options - Configuration for the resolver decorator (delegate + GovCloud base URL +
|
|
74
|
+
* optional extra headers + optional enabled gate).
|
|
75
|
+
* @returns A `ChatGenerationsFallback` bag carrying both the resolver and builder map,
|
|
76
|
+
* typed for direct assignability to the manager + factory.
|
|
77
|
+
*
|
|
78
|
+
* @example
|
|
79
|
+
* ```ts
|
|
80
|
+
* const { connectivityResolver, languageModelBuilders } = createChatGenerationsFallback({
|
|
81
|
+
* delegate: myExistingResolver,
|
|
82
|
+
* baseUrl: 'https://dev.api.gov.salesforce.com/ai/gpt/v1',
|
|
83
|
+
* extraHeaders: { 'x-salesforce-region': 'us-gov-east-1' }, // VERIFY: region header requirement
|
|
84
|
+
* });
|
|
85
|
+
* const factory = new MastraHarnessFactory({ languageModelBuilders });
|
|
86
|
+
* const manager = await createAgentManager(storageRoot, factory, { connectivityResolver });
|
|
87
|
+
* // Agent MUST bind a GovCloud-provisioned GPT model:
|
|
88
|
+
* const agent = await manager.createAgent(projectRoot, { modelId: 'sfdc_ai__DefaultGPT4_8', ... });
|
|
89
|
+
* ```
|
|
90
|
+
*/
|
|
91
|
+
export declare function createChatGenerationsFallback(options: ChatGenerationsResolverOptions): ChatGenerationsFallback;
|