@firststep-studio/sdk 0.7.0 → 0.8.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.
- package/dist/index.d.mts +326 -3
- package/dist/index.d.ts +326 -3
- package/dist/index.js +236 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +233 -12
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/{types-DCrYoOfK.d.mts → types-GoTI_c14.d.mts} +64 -1
- package/dist/{types-DCrYoOfK.d.ts → types-GoTI_c14.d.ts} +64 -1
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { C as ChatMessage, S as SchemaDeclarationPayload, P as ProtocolStreamChunk, A as AgentTransitionPayload, R as RoutingClassificationPayload, H as HandoffRequestPayload, a as HandoffReturnPayload, b as HandoffOfferPayload } from './types-
|
|
2
|
-
export {
|
|
1
|
+
import { C as ChatMessage, S as SchemaDeclarationPayload, P as ProtocolStreamChunk, A as AgentTransitionPayload, R as RoutingClassificationPayload, H as HandoffRequestPayload, a as HandoffReturnPayload, b as HandoffOfferPayload } from './types-GoTI_c14.js';
|
|
2
|
+
export { O as AnalyticsContext, E as ChatbotInfo, F as ClassifierConfig, D as DeploymentInfo, G as FormData, V as FormFieldDefinition, W as FormFieldType, J as FormFieldValue, U as FormSchema, n as HandlerInfo, _ as HandoffContext, f as HandoffInboundContext, $ as HandoffOptions, x as Helpline, w as HelplineResult, v as HelplineSearchOptions, I as IntegrationContext, u as IntegrationResult, Q as InteractionEvent, T as InteractionEventType, t as KnowledgeColumnMetadata, K as KnowledgeContext, s as KnowledgeMetadata, r as KnowledgeResult, L as LoggerContext, m as ProtocolCapabilities, o as ProtocolContext, k as ProtocolError, j as ProtocolFieldValidation, g as ProtocolForm, h as ProtocolFormField, i as ProtocolFormOption, l as ProtocolHandler, X as ProtocolRegistration, c as ProtocolRequest, d as ProtocolResponse, B as RoutingDecision, M as RoutingLog, Y as SchemaAgent, Z as SchemaQuestion, p as SessionContext, N as SessionMetadata, q as SessionState, e as SessionStatus, y as StorageContext, z as StorageSetOptions } from './types-GoTI_c14.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* UCP (Universal Classification Protocol) Types
|
|
@@ -66,6 +66,12 @@ interface UCPErrorResponse {
|
|
|
66
66
|
}
|
|
67
67
|
/**
|
|
68
68
|
* UCP Classifier Info Response
|
|
69
|
+
*
|
|
70
|
+
* Returned by `GET /classifiers/:id/info`. Carries enough metadata for a
|
|
71
|
+
* caller to evaluate routing conditions (level severity comparisons,
|
|
72
|
+
* category lookups) without having to read the classifier definition out
|
|
73
|
+
* of the underlying datastore. Platform-internal handlers should treat
|
|
74
|
+
* this endpoint as the canonical source of classifier shape.
|
|
69
75
|
*/
|
|
70
76
|
interface UCPInfoResponse {
|
|
71
77
|
/** Classifier name */
|
|
@@ -92,6 +98,49 @@ interface UCPInfoResponse {
|
|
|
92
98
|
pricePerRequest?: number;
|
|
93
99
|
currency?: string;
|
|
94
100
|
};
|
|
101
|
+
/**
|
|
102
|
+
* Classifier id. Useful when the caller has only a URL and wants to
|
|
103
|
+
* round-trip the id through the response (some UCP servers serve
|
|
104
|
+
* multiple classifiers behind one base URL).
|
|
105
|
+
*/
|
|
106
|
+
id?: string;
|
|
107
|
+
/**
|
|
108
|
+
* Classifier kind. Lets callers branch on type-specific quirks (e.g.
|
|
109
|
+
* rubric-based classifiers expose `factors`; case-based don't).
|
|
110
|
+
*/
|
|
111
|
+
type?: 'case-based' | 'rubric-based' | 'reference' | 'lightweight' | 'custom';
|
|
112
|
+
/**
|
|
113
|
+
* Ordered list of severity levels emitted by the classifier. Order is
|
|
114
|
+
* the canonical priority order (`position` ascending). Routing-condition
|
|
115
|
+
* evaluators compare a classification result's `level` against this list
|
|
116
|
+
* to honor `>=` / `<=` operators on level labels.
|
|
117
|
+
*/
|
|
118
|
+
levels?: Array<{
|
|
119
|
+
id: string;
|
|
120
|
+
position: number;
|
|
121
|
+
label: string;
|
|
122
|
+
threshold?: number;
|
|
123
|
+
description?: string;
|
|
124
|
+
color?: string;
|
|
125
|
+
}>;
|
|
126
|
+
/**
|
|
127
|
+
* Categories the classifier emits. Mostly used for UI / display, but
|
|
128
|
+
* surfaced here so handlers can present category labels without a
|
|
129
|
+
* second metadata lookup.
|
|
130
|
+
*/
|
|
131
|
+
categories?: Array<{
|
|
132
|
+
id: string;
|
|
133
|
+
label: string;
|
|
134
|
+
description?: string;
|
|
135
|
+
position?: number;
|
|
136
|
+
emoji?: string;
|
|
137
|
+
}>;
|
|
138
|
+
/**
|
|
139
|
+
* Default conversation-turn window the classifier uses. Useful when a
|
|
140
|
+
* caller needs to truncate history before classifying (mirrors the
|
|
141
|
+
* Studio classifier model's `conversationTurns` field).
|
|
142
|
+
*/
|
|
143
|
+
conversationTurns?: number;
|
|
95
144
|
}
|
|
96
145
|
/**
|
|
97
146
|
* UCP Client configuration
|
|
@@ -557,6 +606,57 @@ interface PlatformClientConfig {
|
|
|
557
606
|
/** Request timeout in ms (default: 10000) */
|
|
558
607
|
timeout?: number;
|
|
559
608
|
}
|
|
609
|
+
interface PlatformOrganization {
|
|
610
|
+
/** Studio organization ID */
|
|
611
|
+
id: string;
|
|
612
|
+
/** Display name (null if the organization record has been deleted) */
|
|
613
|
+
name: string | null;
|
|
614
|
+
/** URL-safe slug */
|
|
615
|
+
slug: string | null;
|
|
616
|
+
}
|
|
617
|
+
interface PlatformTokenInfo {
|
|
618
|
+
/** API token ID */
|
|
619
|
+
id: string;
|
|
620
|
+
/** Human-friendly token label */
|
|
621
|
+
name: string;
|
|
622
|
+
/** Authorized scopes */
|
|
623
|
+
scopes: string[];
|
|
624
|
+
/** Project IDs this token is scoped to (null = full org access) */
|
|
625
|
+
projectIds: string[] | null;
|
|
626
|
+
}
|
|
627
|
+
interface PlatformIdentity {
|
|
628
|
+
organization: PlatformOrganization;
|
|
629
|
+
token: PlatformTokenInfo;
|
|
630
|
+
}
|
|
631
|
+
interface PlatformChatbotRegisterInput {
|
|
632
|
+
/** Stable handler-side identifier used to upsert (e.g. handler config ID). */
|
|
633
|
+
externalId: string;
|
|
634
|
+
/** Display name shown in Studio's chatbot list. */
|
|
635
|
+
name: string;
|
|
636
|
+
/** Optional human description. */
|
|
637
|
+
description?: string;
|
|
638
|
+
/** Public URL where Studio should send protocol requests. */
|
|
639
|
+
handlerUrl: string;
|
|
640
|
+
/** Slug for multi-config routing on the handler side. */
|
|
641
|
+
configSlug?: string;
|
|
642
|
+
/** Optional capability flags advertised to Studio. */
|
|
643
|
+
capabilities?: {
|
|
644
|
+
streaming?: boolean;
|
|
645
|
+
formQuestions?: boolean;
|
|
646
|
+
knowledgeActions?: boolean;
|
|
647
|
+
integrations?: boolean;
|
|
648
|
+
[key: string]: unknown;
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
interface PlatformChatbotRegistration {
|
|
652
|
+
/** Studio chatbot MongoDB ID (immutable across re-registrations). */
|
|
653
|
+
id: string;
|
|
654
|
+
/** Echoes back the externalId used as upsert key. */
|
|
655
|
+
externalId: string;
|
|
656
|
+
/** Resolved name as stored in Studio. */
|
|
657
|
+
name: string;
|
|
658
|
+
protocolConfig: Record<string, unknown>;
|
|
659
|
+
}
|
|
560
660
|
interface PlatformChatbot {
|
|
561
661
|
/** Studio chatbot MongoDB ID */
|
|
562
662
|
id: string;
|
|
@@ -578,11 +678,31 @@ declare class PlatformClient {
|
|
|
578
678
|
private token;
|
|
579
679
|
private timeout;
|
|
580
680
|
constructor(config: PlatformClientConfig);
|
|
681
|
+
private request;
|
|
682
|
+
/**
|
|
683
|
+
* Resolve the organization and token metadata for the configured token.
|
|
684
|
+
* Useful for connection state UI ("Connected to {orgName}") and audit.
|
|
685
|
+
*/
|
|
686
|
+
getMe(): Promise<PlatformIdentity>;
|
|
581
687
|
/**
|
|
582
688
|
* List chatbots accessible to this handler's organization.
|
|
583
689
|
* Studio resolves the organization from the API token.
|
|
584
690
|
*/
|
|
585
691
|
listChatbots(): Promise<PlatformChatbot[]>;
|
|
692
|
+
/**
|
|
693
|
+
* Register or update an external-handler chatbot under the token's
|
|
694
|
+
* organization. Idempotent: identified by `externalId`, so handlers
|
|
695
|
+
* can call this on every config save without creating duplicates.
|
|
696
|
+
*/
|
|
697
|
+
registerChatbot(input: PlatformChatbotRegisterInput): Promise<PlatformChatbotRegistration>;
|
|
698
|
+
/**
|
|
699
|
+
* Mark the chatbot identified by `externalId` as disconnected.
|
|
700
|
+
* The Studio record is preserved (history, analytics survive).
|
|
701
|
+
*/
|
|
702
|
+
unregisterChatbot(externalId: string): Promise<{
|
|
703
|
+
ok: boolean;
|
|
704
|
+
modified: number;
|
|
705
|
+
}>;
|
|
586
706
|
}
|
|
587
707
|
/**
|
|
588
708
|
* Create a PlatformClient for calling back to FirstStep Studio.
|
|
@@ -653,4 +773,207 @@ declare function createAuthHeader(token: string): string;
|
|
|
653
773
|
*/
|
|
654
774
|
declare function isValidToken(token: string): boolean;
|
|
655
775
|
|
|
656
|
-
|
|
776
|
+
/**
|
|
777
|
+
* LLM Client
|
|
778
|
+
*
|
|
779
|
+
* SDK client for calling Studio's `/api/llm/*` proxy routes. Studio forwards
|
|
780
|
+
* the request body 1:1 to Google's Generative Language REST API, so the
|
|
781
|
+
* shapes here mirror Google's REST shape (NOT the @google/genai SDK shape).
|
|
782
|
+
*
|
|
783
|
+
* Reference: https://ai.google.dev/api/generate-content
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* ```typescript
|
|
787
|
+
* import { createLLMClient } from '@firststep-studio/sdk';
|
|
788
|
+
*
|
|
789
|
+
* const llm = createLLMClient({
|
|
790
|
+
* studioUrl: 'https://studio-api.example.com',
|
|
791
|
+
* token: process.env.FIRSTSTEP_TOKEN!,
|
|
792
|
+
* });
|
|
793
|
+
*
|
|
794
|
+
* // Single-shot
|
|
795
|
+
* const res = await llm.generate({
|
|
796
|
+
* model: 'gemini-3-flash-preview',
|
|
797
|
+
* contents: [{ role: 'user', parts: [{ text: 'Hello' }] }],
|
|
798
|
+
* generationConfig: { temperature: 0.7, maxOutputTokens: 500 },
|
|
799
|
+
* });
|
|
800
|
+
* console.log(res.candidates[0].content.parts[0].text);
|
|
801
|
+
*
|
|
802
|
+
* // Streaming
|
|
803
|
+
* for await (const chunk of llm.generateStream({
|
|
804
|
+
* model: 'gemini-3-flash-preview',
|
|
805
|
+
* contents: [{ role: 'user', parts: [{ text: 'Count to 5' }] }],
|
|
806
|
+
* })) {
|
|
807
|
+
* process.stdout.write(chunk.candidates?.[0]?.content?.parts?.[0]?.text ?? '');
|
|
808
|
+
* }
|
|
809
|
+
*
|
|
810
|
+
* // Cache
|
|
811
|
+
* const cache = await llm.cache.create({
|
|
812
|
+
* model: 'models/gemini-2.5-flash',
|
|
813
|
+
* systemInstruction: { parts: [{ text: 'long system prompt...' }] },
|
|
814
|
+
* ttl: '3600s',
|
|
815
|
+
* });
|
|
816
|
+
* await llm.cache.get(cache.name);
|
|
817
|
+
* await llm.cache.delete(cache.name);
|
|
818
|
+
* ```
|
|
819
|
+
*/
|
|
820
|
+
interface LLMClientConfig {
|
|
821
|
+
/** Studio backend API URL (e.g. https://studio-api.example.com) */
|
|
822
|
+
studioUrl: string;
|
|
823
|
+
/** API token (Bearer fst_<...>) */
|
|
824
|
+
token: string;
|
|
825
|
+
/** Request timeout in ms for non-streaming calls (default: 60000) */
|
|
826
|
+
timeout?: number;
|
|
827
|
+
}
|
|
828
|
+
interface LLMPart {
|
|
829
|
+
text?: string;
|
|
830
|
+
inlineData?: {
|
|
831
|
+
mimeType: string;
|
|
832
|
+
data: string;
|
|
833
|
+
};
|
|
834
|
+
[k: string]: unknown;
|
|
835
|
+
}
|
|
836
|
+
interface LLMContent {
|
|
837
|
+
role?: 'user' | 'model';
|
|
838
|
+
parts: LLMPart[];
|
|
839
|
+
}
|
|
840
|
+
interface LLMGenerationConfig {
|
|
841
|
+
temperature?: number;
|
|
842
|
+
maxOutputTokens?: number;
|
|
843
|
+
topP?: number;
|
|
844
|
+
topK?: number;
|
|
845
|
+
responseMimeType?: string;
|
|
846
|
+
responseSchema?: Record<string, unknown>;
|
|
847
|
+
thinkingConfig?: {
|
|
848
|
+
thinkingBudget?: number;
|
|
849
|
+
thinkingLevel?: 'minimal' | 'low' | 'medium' | 'high';
|
|
850
|
+
includeThoughts?: boolean;
|
|
851
|
+
};
|
|
852
|
+
stopSequences?: string[];
|
|
853
|
+
[k: string]: unknown;
|
|
854
|
+
}
|
|
855
|
+
interface LLMSafetySetting {
|
|
856
|
+
category: string;
|
|
857
|
+
threshold: string;
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Request body for /api/llm/generate and /api/llm/generate/stream.
|
|
861
|
+
* Mirrors Google's `:generateContent` REST request, plus `model` at the top level
|
|
862
|
+
* so Studio can build the URL path.
|
|
863
|
+
*/
|
|
864
|
+
interface LLMGenerateRequest {
|
|
865
|
+
model: string;
|
|
866
|
+
contents: LLMContent[];
|
|
867
|
+
systemInstruction?: {
|
|
868
|
+
parts: LLMPart[];
|
|
869
|
+
};
|
|
870
|
+
generationConfig?: LLMGenerationConfig;
|
|
871
|
+
safetySettings?: LLMSafetySetting[];
|
|
872
|
+
/** Resource name of an explicit cache, e.g. "cachedContents/abc123" */
|
|
873
|
+
cachedContent?: string;
|
|
874
|
+
/** Function-calling tools (forwarded as-is) */
|
|
875
|
+
tools?: unknown[];
|
|
876
|
+
[k: string]: unknown;
|
|
877
|
+
}
|
|
878
|
+
interface LLMUsageMetadata {
|
|
879
|
+
promptTokenCount?: number;
|
|
880
|
+
candidatesTokenCount?: number;
|
|
881
|
+
cachedContentTokenCount?: number;
|
|
882
|
+
thoughtsTokenCount?: number;
|
|
883
|
+
totalTokenCount?: number;
|
|
884
|
+
[k: string]: unknown;
|
|
885
|
+
}
|
|
886
|
+
interface LLMCandidate {
|
|
887
|
+
content?: LLMContent;
|
|
888
|
+
finishReason?: string;
|
|
889
|
+
index?: number;
|
|
890
|
+
safetyRatings?: unknown[];
|
|
891
|
+
[k: string]: unknown;
|
|
892
|
+
}
|
|
893
|
+
interface LLMGenerateResponse {
|
|
894
|
+
candidates?: LLMCandidate[];
|
|
895
|
+
usageMetadata?: LLMUsageMetadata;
|
|
896
|
+
promptFeedback?: {
|
|
897
|
+
blockReason?: string;
|
|
898
|
+
[k: string]: unknown;
|
|
899
|
+
};
|
|
900
|
+
modelVersion?: string;
|
|
901
|
+
responseId?: string;
|
|
902
|
+
[k: string]: unknown;
|
|
903
|
+
}
|
|
904
|
+
interface LLMCacheCreateRequest {
|
|
905
|
+
model: string;
|
|
906
|
+
displayName?: string;
|
|
907
|
+
systemInstruction?: {
|
|
908
|
+
parts: LLMPart[];
|
|
909
|
+
};
|
|
910
|
+
contents?: LLMContent[];
|
|
911
|
+
/** Time-to-live, e.g. "3600s" */
|
|
912
|
+
ttl?: string;
|
|
913
|
+
[k: string]: unknown;
|
|
914
|
+
}
|
|
915
|
+
interface LLMCachedContent {
|
|
916
|
+
name: string;
|
|
917
|
+
model: string;
|
|
918
|
+
createTime?: string;
|
|
919
|
+
updateTime?: string;
|
|
920
|
+
expireTime?: string;
|
|
921
|
+
displayName?: string;
|
|
922
|
+
usageMetadata?: {
|
|
923
|
+
totalTokenCount?: number;
|
|
924
|
+
};
|
|
925
|
+
[k: string]: unknown;
|
|
926
|
+
}
|
|
927
|
+
declare class LLMError extends Error {
|
|
928
|
+
status: number;
|
|
929
|
+
body: unknown;
|
|
930
|
+
constructor(message: string, status: number, body: unknown);
|
|
931
|
+
}
|
|
932
|
+
declare class LLMClient {
|
|
933
|
+
private studioUrl;
|
|
934
|
+
private token;
|
|
935
|
+
private timeout;
|
|
936
|
+
constructor(config: LLMClientConfig);
|
|
937
|
+
private headers;
|
|
938
|
+
/**
|
|
939
|
+
* Single-shot generation. Body is forwarded to Google's :generateContent
|
|
940
|
+
* unchanged. Response is Google's response shape unchanged.
|
|
941
|
+
*/
|
|
942
|
+
generate(req: LLMGenerateRequest): Promise<LLMGenerateResponse>;
|
|
943
|
+
/**
|
|
944
|
+
* Streaming generation as an AsyncGenerator of partial responses.
|
|
945
|
+
* Each yielded chunk has the same shape as a non-streaming response (a
|
|
946
|
+
* `candidates[]` slice with the next text fragment, plus usageMetadata
|
|
947
|
+
* on the final chunk).
|
|
948
|
+
*
|
|
949
|
+
* Note: timeout option is not enforced for streams; consumer should
|
|
950
|
+
* abort externally if needed.
|
|
951
|
+
*/
|
|
952
|
+
generateStream(req: LLMGenerateRequest): AsyncGenerator<LLMGenerateResponse>;
|
|
953
|
+
/**
|
|
954
|
+
* Explicit prompt cache surface. Maps to Google's `/cachedContents` REST API.
|
|
955
|
+
*/
|
|
956
|
+
cache: {
|
|
957
|
+
/**
|
|
958
|
+
* Create an explicit cache. Returns the resource (use `cache.name` as the
|
|
959
|
+
* `cachedContent` field on a future generate() request).
|
|
960
|
+
*/
|
|
961
|
+
create: (req: LLMCacheCreateRequest) => Promise<LLMCachedContent>;
|
|
962
|
+
/**
|
|
963
|
+
* Fetch a cache by resource name (e.g. "cachedContents/abc123").
|
|
964
|
+
* Returns null when the cache is not found (404), which is the common
|
|
965
|
+
* "expired" signal — callers can simply recreate.
|
|
966
|
+
*/
|
|
967
|
+
get: (name: string) => Promise<LLMCachedContent | null>;
|
|
968
|
+
/**
|
|
969
|
+
* Delete a cache by resource name. Idempotent: 404 is treated as success.
|
|
970
|
+
*/
|
|
971
|
+
delete: (name: string) => Promise<void>;
|
|
972
|
+
};
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Create an LLMClient that talks to Studio's /api/llm/* proxy routes.
|
|
976
|
+
*/
|
|
977
|
+
declare function createLLMClient(config: LLMClientConfig): LLMClient;
|
|
978
|
+
|
|
979
|
+
export { AgentTransitionPayload, ChatMessage, type ClassificationResult, type EmergencyPayload, HandoffOfferPayload, HandoffRequestPayload, HandoffReturnPayload, type HelplineCardItem, type HelplineCardPayload, type LLMCacheCreateRequest, type LLMCachedContent, type LLMCandidate, LLMClient, type LLMClientConfig, type LLMContent, LLMError, type LLMGenerateRequest, type LLMGenerateResponse, type LLMGenerationConfig, type LLMPart, type LLMSafetySetting, type LLMUsageMetadata, MARKER_TYPES, type MarkerType, type PlatformChatbot, PlatformClient, type PlatformClientConfig, PlatformError, ProtocolStreamChunk, type ProviderCardItem, type ProviderCardPayload, type ReportCardPayload, type ResourceCardItem, type ResourceCardPayload, RoutingClassificationPayload, type SafetyPlanPayload, type SafetyPlanSection, SchemaDeclarationPayload, type UCPClassifyRequest, type UCPClassifyResponse, UCPClient, type UCPClientConfig, type UCPEndpointConfig, UCPError, type UCPErrorResponse, type UCPInfoResponse, type UCPMessage, classifyWithUCP, createAuthHeader, createLLMClient, createPlatformClient, createRequestSignature, createUCPClient, isValidToken, renderMarkers, streamMetadata, verifyRequestSignature };
|
package/dist/index.js
CHANGED
|
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
20
20
|
// src/index.ts
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
|
+
LLMClient: () => LLMClient,
|
|
24
|
+
LLMError: () => LLMError,
|
|
23
25
|
MARKER_TYPES: () => MARKER_TYPES,
|
|
24
26
|
PlatformClient: () => PlatformClient,
|
|
25
27
|
PlatformError: () => PlatformError,
|
|
@@ -27,6 +29,7 @@ __export(index_exports, {
|
|
|
27
29
|
UCPError: () => UCPError,
|
|
28
30
|
classifyWithUCP: () => classifyWithUCP,
|
|
29
31
|
createAuthHeader: () => createAuthHeader,
|
|
32
|
+
createLLMClient: () => createLLMClient,
|
|
30
33
|
createPlatformClient: () => createPlatformClient,
|
|
31
34
|
createRequestSignature: () => createRequestSignature,
|
|
32
35
|
createUCPClient: () => createUCPClient,
|
|
@@ -339,29 +342,64 @@ var PlatformClient = class {
|
|
|
339
342
|
this.token = config.token;
|
|
340
343
|
this.timeout = config.timeout || 1e4;
|
|
341
344
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
*/
|
|
346
|
-
async listChatbots() {
|
|
347
|
-
const res = await fetch(`${this.studioUrl}/api/protocol/chatbots`, {
|
|
348
|
-
method: "GET",
|
|
345
|
+
async request(path, errorPrefix, init = {}) {
|
|
346
|
+
const res = await fetch(`${this.studioUrl}${path}`, {
|
|
347
|
+
method: init.method || "GET",
|
|
349
348
|
headers: {
|
|
350
349
|
"Authorization": `Bearer ${this.token}`,
|
|
351
350
|
"Content-Type": "application/json"
|
|
352
351
|
},
|
|
352
|
+
body: init.body !== void 0 ? JSON.stringify(init.body) : void 0,
|
|
353
353
|
signal: AbortSignal.timeout(this.timeout)
|
|
354
354
|
});
|
|
355
355
|
if (!res.ok) {
|
|
356
356
|
const body = await res.text().catch(() => "");
|
|
357
|
-
throw new PlatformError(
|
|
358
|
-
`Failed to list chatbots: ${res.status} ${body}`,
|
|
359
|
-
res.status
|
|
360
|
-
);
|
|
357
|
+
throw new PlatformError(`${errorPrefix}: ${res.status} ${body}`, res.status);
|
|
361
358
|
}
|
|
362
|
-
|
|
359
|
+
if (res.status === 204) return void 0;
|
|
360
|
+
return res.json();
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Resolve the organization and token metadata for the configured token.
|
|
364
|
+
* Useful for connection state UI ("Connected to {orgName}") and audit.
|
|
365
|
+
*/
|
|
366
|
+
async getMe() {
|
|
367
|
+
return this.request("/api/protocol/me", "Failed to fetch identity");
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* List chatbots accessible to this handler's organization.
|
|
371
|
+
* Studio resolves the organization from the API token.
|
|
372
|
+
*/
|
|
373
|
+
async listChatbots() {
|
|
374
|
+
const data = await this.request(
|
|
375
|
+
"/api/protocol/chatbots",
|
|
376
|
+
"Failed to list chatbots"
|
|
377
|
+
);
|
|
363
378
|
return data.chatbots;
|
|
364
379
|
}
|
|
380
|
+
/**
|
|
381
|
+
* Register or update an external-handler chatbot under the token's
|
|
382
|
+
* organization. Idempotent: identified by `externalId`, so handlers
|
|
383
|
+
* can call this on every config save without creating duplicates.
|
|
384
|
+
*/
|
|
385
|
+
async registerChatbot(input) {
|
|
386
|
+
return this.request(
|
|
387
|
+
"/api/protocol/chatbots",
|
|
388
|
+
"Failed to register chatbot",
|
|
389
|
+
{ method: "POST", body: input }
|
|
390
|
+
);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Mark the chatbot identified by `externalId` as disconnected.
|
|
394
|
+
* The Studio record is preserved (history, analytics survive).
|
|
395
|
+
*/
|
|
396
|
+
async unregisterChatbot(externalId) {
|
|
397
|
+
return this.request(
|
|
398
|
+
`/api/protocol/chatbots/${encodeURIComponent(externalId)}`,
|
|
399
|
+
"Failed to unregister chatbot",
|
|
400
|
+
{ method: "DELETE" }
|
|
401
|
+
);
|
|
402
|
+
}
|
|
365
403
|
};
|
|
366
404
|
function createPlatformClient(config) {
|
|
367
405
|
return new PlatformClient(config);
|
|
@@ -398,8 +436,193 @@ function createAuthHeader(token) {
|
|
|
398
436
|
function isValidToken(token) {
|
|
399
437
|
return typeof token === "string" && token.startsWith(TOKEN_PREFIX) && token.length === TOKEN_LENGTH;
|
|
400
438
|
}
|
|
439
|
+
|
|
440
|
+
// src/llm.ts
|
|
441
|
+
var LLMError = class extends Error {
|
|
442
|
+
constructor(message, status, body) {
|
|
443
|
+
super(message);
|
|
444
|
+
this.name = "LLMError";
|
|
445
|
+
this.status = status;
|
|
446
|
+
this.body = body;
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
var LLMClient = class {
|
|
450
|
+
constructor(config) {
|
|
451
|
+
/**
|
|
452
|
+
* Explicit prompt cache surface. Maps to Google's `/cachedContents` REST API.
|
|
453
|
+
*/
|
|
454
|
+
this.cache = {
|
|
455
|
+
/**
|
|
456
|
+
* Create an explicit cache. Returns the resource (use `cache.name` as the
|
|
457
|
+
* `cachedContent` field on a future generate() request).
|
|
458
|
+
*/
|
|
459
|
+
create: async (req) => {
|
|
460
|
+
const res = await fetch(`${this.studioUrl}/api/llm/cache`, {
|
|
461
|
+
method: "POST",
|
|
462
|
+
headers: this.headers(),
|
|
463
|
+
body: JSON.stringify(req),
|
|
464
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
465
|
+
});
|
|
466
|
+
const text = await res.text();
|
|
467
|
+
let body;
|
|
468
|
+
try {
|
|
469
|
+
body = text ? JSON.parse(text) : void 0;
|
|
470
|
+
} catch {
|
|
471
|
+
body = { raw: text };
|
|
472
|
+
}
|
|
473
|
+
if (!res.ok) {
|
|
474
|
+
throw new LLMError(`LLM cache.create failed: ${res.status}`, res.status, body);
|
|
475
|
+
}
|
|
476
|
+
return body;
|
|
477
|
+
},
|
|
478
|
+
/**
|
|
479
|
+
* Fetch a cache by resource name (e.g. "cachedContents/abc123").
|
|
480
|
+
* Returns null when the cache is not found (404), which is the common
|
|
481
|
+
* "expired" signal — callers can simply recreate.
|
|
482
|
+
*/
|
|
483
|
+
get: async (name) => {
|
|
484
|
+
const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {
|
|
485
|
+
method: "GET",
|
|
486
|
+
headers: this.headers(),
|
|
487
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
488
|
+
});
|
|
489
|
+
if (res.status === 404) return null;
|
|
490
|
+
const text = await res.text();
|
|
491
|
+
let body;
|
|
492
|
+
try {
|
|
493
|
+
body = text ? JSON.parse(text) : void 0;
|
|
494
|
+
} catch {
|
|
495
|
+
body = { raw: text };
|
|
496
|
+
}
|
|
497
|
+
if (!res.ok) {
|
|
498
|
+
throw new LLMError(`LLM cache.get failed: ${res.status}`, res.status, body);
|
|
499
|
+
}
|
|
500
|
+
return body;
|
|
501
|
+
},
|
|
502
|
+
/**
|
|
503
|
+
* Delete a cache by resource name. Idempotent: 404 is treated as success.
|
|
504
|
+
*/
|
|
505
|
+
delete: async (name) => {
|
|
506
|
+
const res = await fetch(`${this.studioUrl}/api/llm/cache/${encodeURIComponent(name)}`, {
|
|
507
|
+
method: "DELETE",
|
|
508
|
+
headers: this.headers(),
|
|
509
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
510
|
+
});
|
|
511
|
+
if (res.ok || res.status === 404) return;
|
|
512
|
+
const text = await res.text().catch(() => "");
|
|
513
|
+
let body;
|
|
514
|
+
try {
|
|
515
|
+
body = text ? JSON.parse(text) : void 0;
|
|
516
|
+
} catch {
|
|
517
|
+
body = { raw: text };
|
|
518
|
+
}
|
|
519
|
+
throw new LLMError(`LLM cache.delete failed: ${res.status}`, res.status, body);
|
|
520
|
+
}
|
|
521
|
+
};
|
|
522
|
+
this.studioUrl = config.studioUrl.replace(/\/+$/, "");
|
|
523
|
+
this.token = config.token;
|
|
524
|
+
this.timeout = config.timeout ?? 6e4;
|
|
525
|
+
}
|
|
526
|
+
headers() {
|
|
527
|
+
return {
|
|
528
|
+
"Authorization": `Bearer ${this.token}`,
|
|
529
|
+
"Content-Type": "application/json"
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Single-shot generation. Body is forwarded to Google's :generateContent
|
|
534
|
+
* unchanged. Response is Google's response shape unchanged.
|
|
535
|
+
*/
|
|
536
|
+
async generate(req) {
|
|
537
|
+
const res = await fetch(`${this.studioUrl}/api/llm/generate`, {
|
|
538
|
+
method: "POST",
|
|
539
|
+
headers: this.headers(),
|
|
540
|
+
body: JSON.stringify(req),
|
|
541
|
+
signal: AbortSignal.timeout(this.timeout)
|
|
542
|
+
});
|
|
543
|
+
const text = await res.text();
|
|
544
|
+
let body;
|
|
545
|
+
try {
|
|
546
|
+
body = text ? JSON.parse(text) : void 0;
|
|
547
|
+
} catch {
|
|
548
|
+
body = { raw: text };
|
|
549
|
+
}
|
|
550
|
+
if (!res.ok) {
|
|
551
|
+
throw new LLMError(`LLM generate failed: ${res.status}`, res.status, body);
|
|
552
|
+
}
|
|
553
|
+
return body;
|
|
554
|
+
}
|
|
555
|
+
/**
|
|
556
|
+
* Streaming generation as an AsyncGenerator of partial responses.
|
|
557
|
+
* Each yielded chunk has the same shape as a non-streaming response (a
|
|
558
|
+
* `candidates[]` slice with the next text fragment, plus usageMetadata
|
|
559
|
+
* on the final chunk).
|
|
560
|
+
*
|
|
561
|
+
* Note: timeout option is not enforced for streams; consumer should
|
|
562
|
+
* abort externally if needed.
|
|
563
|
+
*/
|
|
564
|
+
async *generateStream(req) {
|
|
565
|
+
const res = await fetch(`${this.studioUrl}/api/llm/generate/stream`, {
|
|
566
|
+
method: "POST",
|
|
567
|
+
headers: this.headers(),
|
|
568
|
+
body: JSON.stringify(req)
|
|
569
|
+
});
|
|
570
|
+
if (!res.ok || !res.body) {
|
|
571
|
+
const text = await res.text().catch(() => "");
|
|
572
|
+
let body;
|
|
573
|
+
try {
|
|
574
|
+
body = text ? JSON.parse(text) : void 0;
|
|
575
|
+
} catch {
|
|
576
|
+
body = { raw: text };
|
|
577
|
+
}
|
|
578
|
+
throw new LLMError(`LLM generateStream failed: ${res.status}`, res.status, body);
|
|
579
|
+
}
|
|
580
|
+
const reader = res.body.getReader();
|
|
581
|
+
const decoder = new TextDecoder();
|
|
582
|
+
let buffer = "";
|
|
583
|
+
try {
|
|
584
|
+
while (true) {
|
|
585
|
+
const { done, value } = await reader.read();
|
|
586
|
+
if (done) break;
|
|
587
|
+
buffer += decoder.decode(value, { stream: true });
|
|
588
|
+
const lines = buffer.split("\n");
|
|
589
|
+
buffer = lines.pop() ?? "";
|
|
590
|
+
for (const line of lines) {
|
|
591
|
+
const t2 = line.trim();
|
|
592
|
+
if (!t2.startsWith("data: ")) continue;
|
|
593
|
+
const payload = t2.slice(6);
|
|
594
|
+
if (payload === "[DONE]") continue;
|
|
595
|
+
try {
|
|
596
|
+
yield JSON.parse(payload);
|
|
597
|
+
} catch {
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
const t = buffer.trim();
|
|
602
|
+
if (t.startsWith("data: ")) {
|
|
603
|
+
const payload = t.slice(6);
|
|
604
|
+
if (payload && payload !== "[DONE]") {
|
|
605
|
+
try {
|
|
606
|
+
yield JSON.parse(payload);
|
|
607
|
+
} catch {
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
} finally {
|
|
612
|
+
try {
|
|
613
|
+
reader.releaseLock();
|
|
614
|
+
} catch {
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
};
|
|
619
|
+
function createLLMClient(config) {
|
|
620
|
+
return new LLMClient(config);
|
|
621
|
+
}
|
|
401
622
|
// Annotate the CommonJS export names for ESM import in node:
|
|
402
623
|
0 && (module.exports = {
|
|
624
|
+
LLMClient,
|
|
625
|
+
LLMError,
|
|
403
626
|
MARKER_TYPES,
|
|
404
627
|
PlatformClient,
|
|
405
628
|
PlatformError,
|
|
@@ -407,6 +630,7 @@ function isValidToken(token) {
|
|
|
407
630
|
UCPError,
|
|
408
631
|
classifyWithUCP,
|
|
409
632
|
createAuthHeader,
|
|
633
|
+
createLLMClient,
|
|
410
634
|
createPlatformClient,
|
|
411
635
|
createRequestSignature,
|
|
412
636
|
createUCPClient,
|