@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.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-DCrYoOfK.js';
2
- export { M as AnalyticsContext, z as ChatbotInfo, B as ClassifierConfig, D as DeploymentInfo, F as FormData, T as FormFieldDefinition, U as FormFieldType, E as FormFieldValue, Q as FormSchema, n as HandlerInfo, Y as HandoffContext, f as HandoffInboundContext, Z as HandoffOptions, v as Helpline, u as HelplineResult, t as HelplineSearchOptions, I as IntegrationContext, s as IntegrationResult, N as InteractionEvent, O as InteractionEventType, K as KnowledgeContext, 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, V as ProtocolRegistration, c as ProtocolRequest, d as ProtocolResponse, y as RoutingDecision, G as RoutingLog, W as SchemaAgent, X as SchemaQuestion, p as SessionContext, J as SessionMetadata, q as SessionState, e as SessionStatus, w as StorageContext, x as StorageSetOptions } from './types-DCrYoOfK.js';
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
- export { AgentTransitionPayload, ChatMessage, type ClassificationResult, type EmergencyPayload, HandoffOfferPayload, HandoffRequestPayload, HandoffReturnPayload, type HelplineCardItem, type HelplineCardPayload, 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, createPlatformClient, createRequestSignature, createUCPClient, isValidToken, renderMarkers, streamMetadata, verifyRequestSignature };
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
- * List chatbots accessible to this handler's organization.
344
- * Studio resolves the organization from the API token.
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
- const data = await res.json();
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,