@salsita-ai/sdk 0.50.0-develop.0d5f1aa1f7 → 0.50.0-develop.2331d4a6b7

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.
Files changed (21) hide show
  1. package/dist/configurator/application-runtime/extensions/configurator-extension-systems.d.ts +2 -1
  2. package/dist/configurator/application-runtime/extensions/configurator-extension-systems.js +1 -1
  3. package/dist/configurator/application-runtime/extensions/selectors.d.ts +2 -1
  4. package/dist/configurator/application-runtime/extensions/selectors.js +1 -1
  5. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.d.ts +35 -0
  6. package/dist/configurator/application-runtime/extensions/systems/assistant-system/assistant-system.js +1 -0
  7. package/dist/configurator/{assistant → application-runtime/extensions/systems/assistant-system}/browser-details.d.ts +1 -1
  8. package/dist/configurator/application-runtime/extensions/systems/assistant-system/browser-details.js +1 -0
  9. package/dist/configurator/{assistant → application-runtime/extensions/systems/assistant-system}/graphql-tools.d.ts +13 -14
  10. package/dist/configurator/application-runtime/extensions/systems/assistant-system/graphql-tools.js +1 -0
  11. package/dist/configurator/application-runtime/extensions/systems/index.d.ts +1 -0
  12. package/dist/configurator/application-runtime/extensions/systems/index.js +1 -1
  13. package/dist/configurator/assistant/use-assistant-chat.js +1 -1
  14. package/dist/docs/index.json +1 -1
  15. package/dist/docs/phoenix/phoenix-cli.mdx +4 -2
  16. package/dist/docs/phoenix/sdk-builder-workflow.mdx +2 -2
  17. package/dist/phoenix/commands/login.d.ts +24 -6
  18. package/dist/phoenix/commands/login.js +2 -2
  19. package/package.json +3 -3
  20. package/dist/configurator/assistant/browser-details.js +0 -1
  21. package/dist/configurator/assistant/graphql-tools.js +0 -1
@@ -1,5 +1,5 @@
1
1
  import type { $DomainEntity, DomainEntity } from '#package/domain';
2
- import type { AnimationSystem, CameraControlSystem, CameraSystem, CanvasSystem, DragAndDropSystem, ExportSystem, GraphQLSystem, ImageRenderingSystem, LayoutSystem, SketchingSystem } from './systems/index.js';
2
+ import type { AnimationSystem, AssistantSystem, CameraControlSystem, CameraSystem, CanvasSystem, DragAndDropSystem, ExportSystem, GraphQLSystem, ImageRenderingSystem, LayoutSystem, SketchingSystem } from './systems/index.js';
3
3
  /**
4
4
  * The configurator systems of one slot. Every system reaches the domain through runtime selectors,
5
5
  * so its own entity types are all it needs and the erased aliases are what the runtime hands around.
@@ -18,6 +18,7 @@ export interface ConfiguratorExtensionSystems<ENTITIES extends DomainEntity<stri
18
18
  readonly exportSystem: ExportSystem<ENTITIES, ALLOWED_ROOT_ENTITIES, $MAP, NAVIGATION_STATES, VIEW_STATES, TOOL_STATES, EXPORT_STATES>;
19
19
  readonly cameraSystem: CameraSystem<ENTITIES>;
20
20
  readonly graphQLSystem: GraphQLSystem;
21
+ readonly assistantSystem: AssistantSystem;
21
22
  }
22
23
  /** The erased shape the runtime slice constructs; consumers that know their entities narrow on read. */
23
24
  export type AnyConfiguratorExtensionSystems = ConfiguratorExtensionSystems<any, any, any, any, any, any, any>;
@@ -1 +1 @@
1
- let e=["canvasSystem","cameraControlSystem","animationSystem","dragAndDropSystem","imageRenderingSystem","layoutSystem","sketchingSystem","exportSystem","cameraSystem","graphQLSystem"];export const isConfiguratorExtensionSystems=t=>e.every(e=>e in t);
1
+ let e=["canvasSystem","cameraControlSystem","animationSystem","dragAndDropSystem","imageRenderingSystem","layoutSystem","sketchingSystem","exportSystem","cameraSystem","graphQLSystem","assistantSystem"];export const isConfiguratorExtensionSystems=t=>e.every(e=>e in t);
@@ -1,7 +1,7 @@
1
1
  import type { AccessInfoSelectors, DomainContextSelectors, DomainServicesSelectors, SpecSelectors } from '#package/domain';
2
2
  import type { CommonServicesSelectors } from '#package/react-sdk';
3
3
  import type { AnyConfiguratorExtensionSystems } from './configurator-extension-systems.js';
4
- import { AnimationSystem, type AnyCameraSystem, type AnyCanvasSystem, type AnyDragAndDropSystem, type AnyExportSystem, type AnyImageRenderingSystem, type AnyLayoutSystem, type AnySketchingSystem, CameraControlSystem, GraphQLSystem } from './systems/index.js';
4
+ import { AnimationSystem, type AnyCameraSystem, type AnyCanvasSystem, type AnyDragAndDropSystem, type AnyExportSystem, type AnyImageRenderingSystem, type AnyLayoutSystem, type AnySketchingSystem, AssistantSystem, CameraControlSystem, GraphQLSystem } from './systems/index.js';
5
5
  interface Context {
6
6
  readonly domainContextSelectors: DomainContextSelectors;
7
7
  readonly specSelectors: SpecSelectors;
@@ -31,6 +31,7 @@ export declare const createConfiguratorExtensionSelectors: (context: Context) =>
31
31
  getExportSystem: () => AnyExportSystem;
32
32
  getCameraSystem: () => AnyCameraSystem;
33
33
  getGraphQLSystem: () => GraphQLSystem;
34
+ getAssistantSystem: () => AssistantSystem;
34
35
  };
35
36
  export type ConfiguratorExtensionSelectors = ReturnType<typeof createConfiguratorExtensionSelectors>;
36
37
  export {};
@@ -1 +1 @@
1
- import{AnimationSystem as e,CameraControlSystem as t,CameraSystem as n,CanvasSystem as o,DragAndDropSystem as r,ExportSystem as g,GraphQLSystem as s,ImageRenderingSystem as i,LayoutSystem as a,SketchingSystem as m}from"./systems/index.js";export const createConfiguratorExtensionSelectors=S=>{let{domainContextSelectors:y,specSelectors:u,domainServicesSelectors:C,accessSelectors:w,commonServicesSelectors:c}=S,f={getConfigurationId:()=>w.getConfiguratorConfigurationId(),getAuthorizedClient:()=>c.getConfiguratorAuthorizedClient(),domainContextSelectors:y,domainServicesSelectors:C,specSelectors:u},d={getSystems:()=>k,getCanvasSystem:()=>x,getCameraControlSystem:()=>p,getAnimationSystem:()=>A,getDragAndDropSystem:()=>h,getImageRenderingSystem:()=>j,getLayoutSystem:()=>z,getSketchingSystem:()=>D,getExportSystem:()=>E,getCameraSystem:()=>L,getGraphQLSystem:()=>I},l={...f,configuratorExtensionSelectors:d},x=new o(f),p=new t({configuratorExtensionSelectors:d}),h=new r(l),A=new e(f),E=new g(l),I=new s(f),z=new a(f),D=new m(l),L=new n(l),j=new i(l),k={cameraControlSystem:p,layoutSystem:z,sketchingSystem:D,dragAndDropSystem:h,canvasSystem:x,cameraSystem:L,exportSystem:E,imageRenderingSystem:j,animationSystem:A,graphQLSystem:I};return d};
1
+ import{AnimationSystem as e,AssistantSystem as t,CameraControlSystem as n,CameraSystem as o,CanvasSystem as r,DragAndDropSystem as s,ExportSystem as g,GraphQLSystem as i,ImageRenderingSystem as a,LayoutSystem as m,SketchingSystem as S}from"./systems/index.js";export const createConfiguratorExtensionSelectors=y=>{let{domainContextSelectors:u,specSelectors:w,domainServicesSelectors:C,accessSelectors:c,commonServicesSelectors:f}=y,l={getConfigurationId:()=>c.getConfiguratorConfigurationId(),getAuthorizedClient:()=>f.getConfiguratorAuthorizedClient(),domainContextSelectors:u,domainServicesSelectors:C,specSelectors:w},d={getSystems:()=>G,getCanvasSystem:()=>p,getCameraControlSystem:()=>A,getAnimationSystem:()=>h,getDragAndDropSystem:()=>E,getImageRenderingSystem:()=>v,getLayoutSystem:()=>L,getSketchingSystem:()=>j,getExportSystem:()=>I,getCameraSystem:()=>k,getGraphQLSystem:()=>z,getAssistantSystem:()=>D},x={...l,configuratorExtensionSelectors:d},p=new r(l),A=new n({configuratorExtensionSelectors:d}),E=new s(x),h=new e(l),I=new g(x),z=new i(l),D=new t({configuratorExtensionSelectors:d,domainContextSelectors:u,specSelectors:w}),L=new m(l),j=new S(x),k=new o(x),v=new a(x),G={cameraControlSystem:A,layoutSystem:L,sketchingSystem:j,dragAndDropSystem:E,canvasSystem:p,cameraSystem:k,exportSystem:I,imageRenderingSystem:v,animationSystem:h,graphQLSystem:z,assistantSystem:D};return d};
@@ -0,0 +1,35 @@
1
+ import type { AnyClientTool } from '@tanstack/ai';
2
+ import type { DomainContextSelectors, InitializableSystem, SpecSelectors } from '#package/domain';
3
+ import type { ConfiguratorExtensionSelectors } from '../../selectors.js';
4
+ /** A tool of the assistant that the model calls and the browser runs, as the chat client accepts it. */
5
+ export type AssistantClientTool = AnyClientTool;
6
+ /**
7
+ * Constructor context of an `AssistantSystem`: whole selector objects, like its peers take them, so
8
+ * the tools reach the store, the session mode and the `GraphQLSystem` when they are called.
9
+ */
10
+ export interface AssistantSystemContext {
11
+ readonly configuratorExtensionSelectors: ConfiguratorExtensionSelectors;
12
+ readonly domainContextSelectors: DomainContextSelectors;
13
+ readonly specSelectors: SpecSelectors;
14
+ }
15
+ /**
16
+ * Owns the client tools of the conversational assistant: `getBrowserDetails`, `query` and `apply`.
17
+ * They are built once, over the runtime's selectors, and offered only by a configurator whose
18
+ * application spec has an `assistant` section, so a configurator without one has no tools to
19
+ * register with a chat.
20
+ */
21
+ export declare class AssistantSystem implements InitializableSystem {
22
+ private readonly _context;
23
+ private readonly _tools;
24
+ private _isInitialized;
25
+ constructor(_context: AssistantSystemContext);
26
+ initialize: () => void;
27
+ /**
28
+ * The tools to register with the chat: all of them when the application spec has an `assistant`
29
+ * section, none otherwise or before the first initialization. The section is read on every call
30
+ * and not in `initialize()`, because editor hot reload adds or removes it by reconciliation alone.
31
+ * Each answer is always the same array instance, so the getter is a valid snapshot for a hook
32
+ * subscribed to the runtime.
33
+ */
34
+ getTools: () => readonly AssistantClientTool[];
35
+ }
@@ -0,0 +1 @@
1
+ import{createBrowserDetailsClientTool as t}from"./browser-details.js";import{createApplyClientTool as e,createQueryClientTool as s}from"./graphql-tools.js";let i=[];export class AssistantSystem{_context;_tools;_isInitialized=!1;constructor(i){this._context=i;let{configuratorExtensionSelectors:o,domainContextSelectors:l}=i,r={getGraphQLSystem:()=>o.getGraphQLSystem(),getStoreSystem:()=>l.getStoreSystem(),getActiveMode:()=>l.getActiveMode()};this._tools=[t(),s(r),e(r)]}initialize=()=>{this._isInitialized=!0};getTools=()=>this._isInitialized&&this._context.specSelectors.getAssistant()?this._tools:i}
@@ -2,7 +2,7 @@
2
2
  * Browser implementation of the assistant's `getBrowserDetails` tool: draws the browser details onto
3
3
  * a canvas and returns the capture as a PNG image part, so the model reads them from an image.
4
4
  */
5
- export declare const browserDetailsClientTool: import("@tanstack/ai").ClientTool<{
5
+ export declare const createBrowserDetailsClientTool: () => import("@tanstack/ai").ClientTool<{
6
6
  type: string;
7
7
  properties: {};
8
8
  required: never[];
@@ -0,0 +1 @@
1
+ import{browserDetailsTool as e}from"@salsita-ai/contracts/assistant";let t=e=>{let t=[];for(let i=0;i<e.length;i+=64)t.push(e.slice(i,i+64));return t};export const createBrowserDetailsClientTool=()=>e.client(()=>{let e=(()=>{let e=[`User agent: ${navigator.userAgent}`,`Viewport: ${window.innerWidth} x ${window.innerHeight} px`,`Screen: ${window.screen.width} x ${window.screen.height} px`,`Device pixel ratio: ${window.devicePixelRatio}`,`Language: ${navigator.language}`,`Time zone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}`].flatMap(t),i=document.createElement("canvas");i.width=720,i.height=48+26*e.length;let n=i.getContext("2d");if(!n)throw Error("The browser details capture failed because the canvas has no 2D context. Retry in another browser.");return n.fillStyle="#ffffff",n.fillRect(0,0,i.width,i.height),n.fillStyle="#111111",n.font="16px monospace",n.textBaseline="top",e.forEach((e,t)=>n.fillText(e,24,24+26*t)),i.toDataURL("image/png")})();return[{type:"image",source:{type:"data",value:e.slice(e.indexOf(",")+1),mimeType:"image/png"}}]});
@@ -1,14 +1,13 @@
1
1
  import type { DomainEntity, DomainMode, DomainState } from '#package/domain';
2
- import type { GraphQLSystem } from '../application-runtime/index.js';
2
+ import type { GraphQLSystem } from '../graphql-system/graphql-system.js';
3
3
  /**
4
- * What the assistant's client tools need from the running configurator. The chat hook passes it as
5
- * the client context of `useChat`, which captures it when a request starts: a tool call reads the
6
- * systems as they were when its reply began streaming. A runtime replaced in between (dev hot
7
- * reload) fails that call, which is accepted.
4
+ * What the assistant's client tools need from the running configurator, as accessors they call when
5
+ * the model calls them: a tool call always runs against the systems and the session mode current at
6
+ * that moment, never against ones captured when its reply began streaming.
8
7
  */
9
- export interface AssistantToolContext {
10
- readonly graphQLSystem: Pick<GraphQLSystem, 'prepare' | 'execute'>;
11
- readonly storeSystem: {
8
+ export interface AssistantToolDependencies {
9
+ readonly getGraphQLSystem: () => Pick<GraphQLSystem, 'prepare' | 'execute'>;
10
+ readonly getStoreSystem: () => {
12
11
  readonly isInitialized: boolean;
13
12
  readonly stateSlices: {
14
13
  readonly committed: {
@@ -25,7 +24,7 @@ export interface AssistantToolContext {
25
24
  * committed state and drops their emissions, so mutations in the list show a hypothetical state and
26
25
  * the configuration, the undo stack and the saved revision stay as they were.
27
26
  */
28
- export declare const queryClientTool: import("@tanstack/ai").ClientTool<{
27
+ export declare const createQueryClientTool: (dependencies: AssistantToolDependencies) => import("@tanstack/ai").ClientTool<{
29
28
  type: string;
30
29
  properties: {
31
30
  operations: {
@@ -53,7 +52,7 @@ export declare const queryClientTool: import("@tanstack/ai").ClientTool<{
53
52
  };
54
53
  };
55
54
  required: string[];
56
- }, undefined, "query", AssistantToolContext, false, undefined> & {
55
+ }, undefined, "query", unknown, false, undefined> & {
57
56
  inputSchema: {
58
57
  type: string;
59
58
  properties: {
@@ -90,10 +89,10 @@ export declare const queryClientTool: import("@tanstack/ai").ClientTool<{
90
89
  * Browser implementation of the assistant's `apply` tool: hands the operations to
91
90
  * `GraphQLSystem.execute`, which commits their changes as one undo step, and returns its result,
92
91
  * including why nothing was committed when a commit was held or lost to a conflict. Only an owner
93
- * session may change the configuration; the check lives here and not in the system, whose other
94
- * caller, the editor panel, has its own rules.
92
+ * session may change the configuration; the check lives here and not in the `GraphQLSystem`, whose
93
+ * other caller, the editor panel, has its own rules.
95
94
  */
96
- export declare const applyClientTool: import("@tanstack/ai").ClientTool<{
95
+ export declare const createApplyClientTool: (dependencies: AssistantToolDependencies) => import("@tanstack/ai").ClientTool<{
97
96
  type: string;
98
97
  properties: {
99
98
  operations: {
@@ -121,7 +120,7 @@ export declare const applyClientTool: import("@tanstack/ai").ClientTool<{
121
120
  };
122
121
  };
123
122
  required: string[];
124
- }, undefined, "apply", AssistantToolContext, false, undefined> & {
123
+ }, undefined, "apply", unknown, false, undefined> & {
125
124
  inputSchema: {
126
125
  type: string;
127
126
  properties: {
@@ -0,0 +1 @@
1
+ import{AssistantGraphQLToolInput as e,applyTool as t,queryTool as o}from"@salsita-ai/contracts/assistant";let a=e=>({results:[],committed:!1,message:e}),i=e=>JSON.parse(JSON.stringify(e)),s=t=>{let o=e.safeParse(t);if(!o.success){let e=o.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{ok:!1,refusal:a(`Nothing was run, because the tool input is not a list of operations (${e}). Send { "operations": [{ "query": "<one GraphQL operation>" }] } with at least one item.`)}}return{ok:!0,operations:o.data.operations.map(({query:e,variables:t,operationName:o})=>({document:e,variables:t??void 0,operationName:o??void 0}))}};export const createQueryClientTool=e=>o.client(async t=>{let o=s(t);if(!o.ok)return o.refusal;let n=e.getStoreSystem();return n.isInitialized?i({results:(await e.getGraphQLSystem().prepare(n.stateSlices.committed.value.state,o.operations)).map(({result:e})=>e),committed:!1}):a("Nothing was run, because the configuration is not loaded yet, so there is no state to query. Call the tool again once the configurator has finished loading.")});export const createApplyClientTool=e=>t.client(async t=>{if("owner"!==e.getActiveMode())return a("Nothing was run, because changes cannot be applied in this session: the user may view this configuration but not change it. The query tool still works here, including mutations, to show what a change would do without keeping it.");let o=s(t);return o.ok?i(await e.getGraphQLSystem().execute(o.operations)):o.refusal});
@@ -1,4 +1,5 @@
1
1
  export { AnimationSystem, type AnimationSystemContext } from './animation-system.js';
2
+ export { type AssistantClientTool, AssistantSystem, type AssistantSystemContext, } from './assistant-system/assistant-system.js';
2
3
  export { CameraControlSystem, CameraControlType } from './camera-control-system/camera-control-system.js';
3
4
  export { type AnyCameraSystem, CameraSystem, type CameraSystemContext } from './camera-control-system/camera-system.js';
4
5
  export { createCameraControls } from './camera-control-system/create-camera-controls.js';
@@ -1 +1 @@
1
- export{AnimationSystem}from"./animation-system.js";export{CameraControlSystem,CameraControlType}from"./camera-control-system/camera-control-system.js";export{CameraSystem}from"./camera-control-system/camera-system.js";export{createCameraControls}from"./camera-control-system/create-camera-controls.js";export{CanvasSystem}from"./canvas-system.js";export{DragAndDropSystem}from"./drag-and-drop-system/drag-and-drop-system.js";export{ExportSystem}from"./export-system/export-system.js";export{GraphQLSystem}from"./graphql-system/graphql-system.js";export{ImageRenderingSystem}from"./image-rendering-system/image-rendering-system.js";export{LayoutSystem}from"./layout-system/layout-system.js";export{toolbarHelpElementName}from"./layout-system/toolbar-help-element.js";export{SketchingSystem}from"./sketching-system/sketching-system.js";export{LineEndPointUpdateTag,LineLengthUpdateTag,LineLineConnectionUpdateTag,LineRectangleBottomLeftConnectionUpdateTag,LineRectangleBottomRightConnectionUpdateTag,LineRectangleTopLeftConnectionUpdateTag,LineRectangleTopRightConnectionUpdateTag,LineSketchingPointUpdateTag,LineStartPointUpdateTag,RectangleBottomLeftCornerUpdateTag,RectangleBottomRightCornerUpdateTag,RectangleDepthUpdateTag,RectangleSketchingPointUpdateTag,RectangleTopLeftCornerUpdateTag,RectangleTopRightCornerUpdateTag,RectangleWidthUpdateTag,SetInitialSketchingPointPositionTag,SketchingFixTransformationTag,isAnySketchingPointUpdate,isLineEndPointUpdate,isLineLengthUpdate,isLineLineConnectionUpdate,isLineRectangleBottomLeftConnectionUpdate,isLineRectangleBottomRightConnectionUpdate,isLineRectangleTopLeftConnectionUpdate,isLineRectangleTopRightConnectionUpdate,isLineSketchingPointUpdate,isLineStartPointUpdate,isRectangleBottomLeftCornerUpdate,isRectangleBottomRightCornerUpdate,isRectangleDepthUpdate,isRectangleSketchingPointUpdate,isRectangleTopLeftCornerUpdate,isRectangleTopRightCornerUpdate,isRectangleWidthUpdate,isSetInitialLinePositionUpdate,isSetInitialRectanglePositionUpdate,isSketchingFixTransformationUpdate}from"./sketching-system/sketching-updates.js";
1
+ export{AnimationSystem}from"./animation-system.js";export{AssistantSystem}from"./assistant-system/assistant-system.js";export{CameraControlSystem,CameraControlType}from"./camera-control-system/camera-control-system.js";export{CameraSystem}from"./camera-control-system/camera-system.js";export{createCameraControls}from"./camera-control-system/create-camera-controls.js";export{CanvasSystem}from"./canvas-system.js";export{DragAndDropSystem}from"./drag-and-drop-system/drag-and-drop-system.js";export{ExportSystem}from"./export-system/export-system.js";export{GraphQLSystem}from"./graphql-system/graphql-system.js";export{ImageRenderingSystem}from"./image-rendering-system/image-rendering-system.js";export{LayoutSystem}from"./layout-system/layout-system.js";export{toolbarHelpElementName}from"./layout-system/toolbar-help-element.js";export{SketchingSystem}from"./sketching-system/sketching-system.js";export{LineEndPointUpdateTag,LineLengthUpdateTag,LineLineConnectionUpdateTag,LineRectangleBottomLeftConnectionUpdateTag,LineRectangleBottomRightConnectionUpdateTag,LineRectangleTopLeftConnectionUpdateTag,LineRectangleTopRightConnectionUpdateTag,LineSketchingPointUpdateTag,LineStartPointUpdateTag,RectangleBottomLeftCornerUpdateTag,RectangleBottomRightCornerUpdateTag,RectangleDepthUpdateTag,RectangleSketchingPointUpdateTag,RectangleTopLeftCornerUpdateTag,RectangleTopRightCornerUpdateTag,RectangleWidthUpdateTag,SetInitialSketchingPointPositionTag,SketchingFixTransformationTag,isAnySketchingPointUpdate,isLineEndPointUpdate,isLineLengthUpdate,isLineLineConnectionUpdate,isLineRectangleBottomLeftConnectionUpdate,isLineRectangleBottomRightConnectionUpdate,isLineRectangleTopLeftConnectionUpdate,isLineRectangleTopRightConnectionUpdate,isLineSketchingPointUpdate,isLineStartPointUpdate,isRectangleBottomLeftCornerUpdate,isRectangleBottomRightCornerUpdate,isRectangleDepthUpdate,isRectangleSketchingPointUpdate,isRectangleTopLeftCornerUpdate,isRectangleTopRightCornerUpdate,isRectangleWidthUpdate,isSetInitialLinePositionUpdate,isSetInitialRectanglePositionUpdate,isSketchingFixTransformationUpdate}from"./sketching-system/sketching-updates.js";
@@ -1 +1 @@
1
- import{fetchServerSentEvents as t,useChat as e}from"@tanstack/ai-react";import{useCallback as a,useMemo as r}from"react";import{useAuthorizedClient as s,useConfigurationId as n,useDomain as o}from"#package/react-sdk";import{useConfigurator as i}from"../application-runtime/index.js";import{browserDetailsClientTool as c}from"./browser-details.js";import{applyClientTool as p,queryClientTool as l}from"./graphql-tools.js";let m=[c,l,p];export const useAssistantChat=()=>{let c=n(),p=s(),l=r(()=>p.getAssistantChatConnection(c),[p,c]),d=r(()=>{let e=async()=>({headers:await l.headers()}),a=t(l.url,e),r=t(`${l.url}/runs`,e);return{...a,joinRun:r.joinRun}},[l]),{graphQLSystem:u}=i(),{storeSystem:h,domainContextSelectors:f}=o(),{getActiveMode:w}=f,{messages:g,isLoading:j,error:y,sendMessage:C,stop:S,runId:$}=e({threadId:`assistant:${c}`,connection:d,persistence:!0,history:{pageSize:50},tools:m,context:r(()=>({graphQLSystem:u,storeSystem:h,getActiveMode:w}),[u,h,w])});return{messages:g,isLoading:j,error:y,send:a(async t=>{let e={sentAt:new Date().toISOString()};await C({content:t,metadata:e})},[C]),stop:a(async()=>{S(),null!=$&&await fetch(`${l.url}/runs/${encodeURIComponent($)}/cancel`,{method:"POST",headers:await l.headers()})},[l,$,S])}};
1
+ import{fetchServerSentEvents as t,useChat as e}from"@tanstack/ai-react";import{useCallback as a,useMemo as n}from"react";import{useApplicationRuntimeSelector as s,useAuthorizedClient as r,useConfigurationId as o}from"#package/react-sdk";import{useConfigurator as i}from"../application-runtime/index.js";export const useAssistantChat=()=>{let c=o(),p=r(),l=n(()=>p.getAssistantChatConnection(c),[p,c]),u=n(()=>{let e=async()=>({headers:await l.headers()}),a=t(l.url,e),n=t(`${l.url}/runs`,e);return{...a,joinRun:n.joinRun}},[l]),{assistantSystem:d}=i(),{messages:m,isLoading:h,error:f,sendMessage:g,stop:w,runId:y}=e({threadId:`assistant:${c}`,connection:u,persistence:!0,history:{pageSize:50},tools:s(d.getTools)});return{messages:m,isLoading:h,error:f,send:a(async t=>{let e={sentAt:new Date().toISOString()};await g({content:t,metadata:e})},[g]),stop:a(async()=>{w(),null!=y&&await fetch(`${l.url}/runs/${encodeURIComponent(y)}/cancel`,{method:"POST",headers:await l.headers()})},[l,y,w])}};
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-10-08T10:33:10.288Z",
2
+ "generatedAt": "2026-10-08T14:21:48.897Z",
3
3
  "categories": [
4
4
  {
5
5
  "name": "Phoenix",
@@ -569,9 +569,11 @@ SDK-builder workspaces sign the CLI in with a personal API token instead of proj
569
569
 
570
570
  Signs the CLI in to the SDK-builder workspace `<customer>`:
571
571
 
572
- 1. Opens Studio, where you approve the sign-in (use `--no-open` to only print the link).
572
+ 1. Opens Studio, where you name the token and approve the sign-in (use `--no-open` to only print the link). The page shows the workspace, your account, the computer the CLI runs on and how long the link still works. The CLI suggests the name `phoenix CLI on <computer>`; change it so you can tell your machines and agents apart later.
573
573
  2. Studio sends a one-time code back to the CLI on `127.0.0.1`.
574
- 3. The CLI exchanges the code for a token.
574
+ 3. The CLI exchanges the code for a token with the name you approved, and sends the browser back to Studio, which confirms the sign-in.
575
+
576
+ **Deny** in Studio stops the CLI at once: it exits with `The sign-in was denied in Studio` and no token is created. The link works for five minutes and only for the CLI that printed it; after that, Studio shows that it no longer works and the CLI gives up.
575
577
 
576
578
  The token is stored in `~/.phoenix/credentials.json`, readable only by you, per server, together with the Studio URL of the sign-in; `PHOENIX_CREDENTIALS_FILE` moves the file. The token works until you revoke it. Signing in again to the same server revokes the token it replaces; the CLI warns if the server could not revoke it.
577
579
 
@@ -36,11 +36,11 @@ The starter `spec/application.yaml` begins with `project: default`, which means
36
36
  pnpm phoenix login <workspace>
37
37
  ```
38
38
 
39
- `phoenix login` opens Studio, where you approve the sign-in. Studio sends a one-time code back to the CLI on `127.0.0.1`, and the CLI exchanges it for a personal token. The code only works for the CLI that asked for it and expires after five minutes. Use `--no-open` to print the link instead of opening a browser.
39
+ `phoenix login` opens Studio, where you name the token and approve the sign-in. The CLI suggests `phoenix CLI on <computer>`; a name of your own, such as the agent and the machine, tells your tokens apart later. Studio sends a one-time code back to the CLI on `127.0.0.1`, the CLI exchanges it for a personal token and returns the browser to Studio, which confirms the sign-in. The code only works for the CLI that asked for it and expires after five minutes. **Deny** stops the waiting CLI at once and creates no token. Use `--no-open` to print the link instead of opening a browser.
40
40
 
41
41
  The CLI stores the token in `~/.phoenix/credentials.json`, readable only by you, with one entry per server. `PHOENIX_CREDENTIALS_FILE` moves the file. The token acts for you in your workspace until you revoke it. It stops working when you are removed from the workspace or banned.
42
42
 
43
- `pnpm phoenix logout` revokes the token and removes it from the file. Studio's **CLI tokens** page lists your tokens with when they were last used, and revokes any of them, for example one left on another computer.
43
+ `pnpm phoenix logout` revokes the token and removes it from the file. Studio's **CLI tokens** page lists your tokens by the names you gave them, with when they were last used, and revokes any of them, for example one left on another computer. A revoked token leaves the list.
44
44
 
45
45
  `login`, `logout` and `deploy` follow the region, not the `SERVER_URL` in the project's `.env`. `dev` follows the region too when `.env` has no `SERVER_URL`:
46
46
 
@@ -5,19 +5,37 @@ export interface LoginCommandProps {
5
5
  readonly studioUrl: string;
6
6
  readonly open: boolean;
7
7
  }
8
+ type LoginOutcome = {
9
+ readonly signedIn: true;
10
+ } | {
11
+ readonly signedIn: false;
12
+ readonly message: string;
13
+ };
14
+ interface LoginCallback {
15
+ readonly code: string;
16
+ readonly tokenName: string | null;
17
+ readonly finish: (outcome: LoginOutcome) => Promise<void>;
18
+ }
8
19
  interface LoginCallbackListener {
9
20
  readonly port: number;
10
- readonly code: Promise<string>;
21
+ readonly callback: Promise<LoginCallback>;
11
22
  readonly close: () => void;
12
23
  }
24
+ interface LoginCallbackListenerProps {
25
+ readonly state: string;
26
+ readonly studioUrl: string;
27
+ readonly customer: string;
28
+ }
13
29
  /**
14
- * Receives the one-time code from Studio on 127.0.0.1. Only a callback carrying this session's state is accepted,
15
- * so another page cannot feed the CLI a code of its own.
30
+ * Receives Studio's answer on 127.0.0.1: the one-time code of an approval, or a denial. Only a callback carrying
31
+ * this session's state is accepted, so another page cannot feed the CLI a code of its own. The browser ends on a
32
+ * Studio page; its address is built from the CLI's own Studio URL, never from the request.
16
33
  */
17
- export declare const startLoginCallbackListener: (state: string) => Promise<LoginCallbackListener>;
34
+ export declare const startLoginCallbackListener: (props: LoginCallbackListenerProps) => Promise<LoginCallbackListener>;
18
35
  /**
19
- * Signs the CLI in to an SDK-builder workspace: the user approves in Studio, Studio returns a one-time code to this
20
- * CLI's loopback listener, and the CLI exchanges it with its PKCE verifier for an API token.
36
+ * Signs the CLI in to an SDK-builder workspace: the user names the token and approves in Studio, Studio returns a
37
+ * one-time code to this CLI's loopback listener, and the CLI exchanges it with its PKCE verifier for an API token.
38
+ * The browser waits on the callback meanwhile and ends on Studio's page for the outcome.
21
39
  */
22
40
  export declare const runLoginCommand: (props: LoginCommandProps, { logger }: CommandContext) => Promise<void>;
23
41
  export {};
@@ -1,4 +1,4 @@
1
- import{spawn as e}from"node:child_process";import{createHash as t,randomBytes as r}from"node:crypto";import{createServer as o}from"node:http";import{hostname as n}from"node:os";import{createApiBaseClient as i}from"#package/client";import{getServerCredentials as s,saveServerCredentials as a}from"../services/index.js";let l=(e,t)=>`<!doctype html><html><head><meta charset="utf-8"><title>${e}</title></head><body style="font-family:system-ui,sans-serif;display:grid;place-items:center;height:100vh;margin:0"><main style="text-align:center"><h1>${e}</h1><p>${t}</p></main></body></html>`;export const startLoginCallbackListener=async e=>{let t=null,r=new Promise((e,r)=>{t={resolve:e,reject:r}}),n=o((r,o)=>{let n=new URL(r.url??"/","http://127.0.0.1"),i=n.searchParams.get("code");if("/callback"!==n.pathname)return void o.writeHead(404).end();if(n.searchParams.get("state")!==e||!i){o.writeHead(400,{"content-type":"text/html"}),o.end(l("Sign-in failed","This response does not belong to the waiting CLI. Run phoenix login again.")),t?.reject(Error("The sign-in response did not match this CLI session. Run `phoenix login` again."));return}o.writeHead(200,{"content-type":"text/html"}),o.end(l("Phoenix CLI signed in","You can close this tab and return to the terminal.")),t?.resolve(i)}),i=await new Promise((e,t)=>{n.once("error",t),n.listen(0,"127.0.0.1",()=>{let r=n.address();null===r||"string"==typeof r?t(Error("The login callback listener did not get a port. Run `phoenix login` again.")):e(r.port)})}),s=setTimeout(()=>{t?.reject(Error("Nobody approved the sign-in within 5 minutes. Run `phoenix login` again."))},3e5);return{port:i,code:r,close:()=>{clearTimeout(s),n.close()}}};let c=async(e,t)=>{try{let{error:r,response:o}=await i(e).DELETE("/auth/token",{headers:{authorization:`Bearer ${t}`,"content-type":null}});return r&&401!==o.status?r.message:null}catch(e){return e instanceof Error?e.message:String(e)}};export const runLoginCommand=async(o,{logger:l})=>{let d=r(32).toString("base64url"),h=t("sha256").update(d).digest("base64url"),u=r(16).toString("base64url"),m=await startLoginCallbackListener(u);try{let t=new URL("/builder/cli-login",o.studioUrl);t.searchParams.set("customer",o.customer),t.searchParams.set("port",String(m.port)),t.searchParams.set("state",u),t.searchParams.set("challenge",h),l.step(`Signing in to workspace "${o.customer}" on ${o.serverUrl}`),l.info(`Approve the CLI in your browser:
1
+ import{spawn as e}from"node:child_process";import{createHash as t,randomBytes as r}from"node:crypto";import{createServer as o}from"node:http";import{hostname as n}from"node:os";import{createApiBaseClient as s}from"#package/client";import{getServerCredentials as i,saveServerCredentials as a}from"../services/index.js";let l={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"},c=(e,t)=>`<!doctype html><html><head><meta charset="utf-8"><title>${e}</title></head><body style="font-family:system-ui,sans-serif;display:grid;place-items:center;height:100vh;margin:0"><main style="text-align:center"><h1>${e}</h1><p>${t.replace(/[&<>"']/g,e=>l[e]??e)}</p></main></body></html>`,d=(e,t,r,o)=>new Promise(n=>{e.destroyed?n():(e.once("close",n),e.writeHead(t,{...r,connection:"close"}).end(o))});export const startLoginCallbackListener=async e=>{let{state:t,studioUrl:r,customer:n}=e,s=(e,t)=>{let o=new URL(e,r);return o.searchParams.set("customer",n),t&&o.searchParams.set("name",t),o.toString()},i=null,a=!1,l=new Promise((e,t)=>{i={resolve:e,reject:t}}),m=o((e,r)=>{let o=new URL(e.url??"/","http://127.0.0.1"),n=o.searchParams.get("code"),l="access_denied"===o.searchParams.get("error");if("/callback"!==o.pathname)return void r.writeHead(404).end();if(o.searchParams.get("state")!==t||a||!n&&!l){d(r,400,{"content-type":"text/html"},c("Sign-in failed","This response does not belong to the waiting CLI. Run phoenix login again.")),a||i?.reject(Error("The sign-in response did not match this CLI session. Run `phoenix login` again."));return}if(a=!0,l||!n)return void d(r,302,{location:s("/builder/cli-login/denied",null)}).then(()=>{i?.reject(Error("The sign-in was denied in Studio. Run `phoenix login` again to sign in."))});let m=o.searchParams.get("name");i?.resolve({code:n,tokenName:m,finish:e=>e.signedIn?d(r,302,{location:s("/builder/cli-login/done",m)}):d(r,200,{"content-type":"text/html"},c("Sign-in failed",e.message))})}),h=await new Promise((e,t)=>{m.once("error",t),m.listen(0,"127.0.0.1",()=>{let r=m.address();null===r||"string"==typeof r?t(Error("The login callback listener did not get a port. Run `phoenix login` again.")):e(r.port)})}),u=setTimeout(()=>{i?.reject(Error("Nobody approved the sign-in within 5 minutes. Run `phoenix login` again."))},3e5);return{port:h,callback:l,close:()=>{clearTimeout(u),m.close()}}};let m=async(e,t)=>{try{let{error:r,response:o}=await s(e).DELETE("/auth/token",{headers:{authorization:`Bearer ${t}`,"content-type":null}});return r&&401!==o.status?r.message:null}catch(e){return e instanceof Error?e.message:String(e)}},h=async(e,t)=>{let{data:r,error:o}=await s(e.serverUrl).POST("/auth/token",{body:t});if(o||!r)throw Error(`The server did not accept the sign-in: ${o?.message??"no response"}`);if(r.customer.slug!==e.customer)throw await m(e.serverUrl,r.token),Error(`The sign-in was approved in workspace "${r.customer.slug}", not "${e.customer}". Check --region or --studioUrl and run \`phoenix login ${e.customer}\` again.`);let n=await i(e.serverUrl),l=await a(e.serverUrl,{token:r.token,userId:r.userId,customerSlug:r.customer.slug,customerName:r.customer.name,studioUrl:e.studioUrl,createdAt:new Date().toISOString()}),c=n?await m(e.serverUrl,n.token):null;return{customerName:r.customer.name,path:l,revokeError:c}};export const runLoginCommand=async(o,{logger:s})=>{let i=r(32).toString("base64url"),a=t("sha256").update(i).digest("base64url"),l=r(16).toString("base64url"),c=n(),d=`phoenix CLI on ${c}`,m=await startLoginCallbackListener({state:l,studioUrl:o.studioUrl,customer:o.customer});try{let t=new URL("/builder/cli-login",o.studioUrl);t.searchParams.set("customer",o.customer),t.searchParams.set("port",String(m.port)),t.searchParams.set("state",l),t.searchParams.set("challenge",a),t.searchParams.set("name",d),t.searchParams.set("host",c),t.searchParams.set("started",String(Date.now())),s.step(`Signing in to workspace "${o.customer}" on ${o.serverUrl}`),s.info(`Approve the CLI in your browser:
2
2
 
3
3
  ${t.toString()}
4
- `),o.open&&(t=>{let[r,o]="darwin"===process.platform?["open",[t]]:"win32"===process.platform?["rundll32",["url.dll,FileProtocolHandler",t]]:["xdg-open",[t]];e(r,o,{stdio:"ignore",detached:!0}).on("error",()=>void 0).unref()})(t.toString());let r=await m.code,{data:p,error:g}=await i(o.serverUrl).POST("/auth/token",{body:{code:r,codeVerifier:d,port:m.port,name:`phoenix CLI on ${n()}`}});if(g||!p)throw Error(`The server did not accept the sign-in: ${g?.message??"no response"}`);if(p.customer.slug!==o.customer)throw await c(o.serverUrl,p.token),Error(`The sign-in was approved in workspace "${p.customer.slug}", not "${o.customer}". Check --region or --studioUrl and run \`phoenix login ${o.customer}\` again.`);let w=await s(o.serverUrl),y=await a(o.serverUrl,{token:p.token,userId:p.userId,customerSlug:p.customer.slug,customerName:p.customer.name,studioUrl:o.studioUrl,createdAt:new Date().toISOString()}),f=w?await c(o.serverUrl,w.token):null;f&&l.warn(`The server did not revoke the token this sign-in replaces (${f}). It stays valid on the server. Contact Phoenix support to revoke it.`),l.success(`Signed in to ${p.customer.name}. The token is stored in ${y}; revoke it with phoenix logout.`)}finally{m.close()}};
4
+ `),o.open&&(t=>{let[r,o]="darwin"===process.platform?["open",[t]]:"win32"===process.platform?["rundll32",["url.dll,FileProtocolHandler",t]]:["xdg-open",[t]];e(r,o,{stdio:"ignore",detached:!0}).on("error",()=>void 0).unref()})(t.toString());let r=await m.callback;try{let e=await h(o,{code:r.code,codeVerifier:i,port:m.port,name:d});e.revokeError&&s.warn(`The server did not revoke the token this sign-in replaces (${e.revokeError}). It stays valid on the server. Contact Phoenix support to revoke it.`),await r.finish({signedIn:!0}),s.success(`Signed in to ${e.customerName}. The token is stored in ${e.path}; revoke it with phoenix logout.`)}catch(e){throw await r.finish({signedIn:!1,message:e instanceof Error?e.message:String(e)}),e}}finally{m.close()}};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salsita-ai/sdk",
3
- "version": "0.50.0-develop.0d5f1aa1f7",
3
+ "version": "0.50.0-develop.2331d4a6b7",
4
4
  "description": "TypeScript SDK and CLI for building, running, and deploying Phoenix configurator applications.",
5
5
  "keywords": [
6
6
  "salsita",
@@ -295,8 +295,8 @@
295
295
  "yaml": "2.9.0",
296
296
  "yargs": "17.7.2",
297
297
  "zod": "3.25.76",
298
- "@salsita-ai/common": "0.50.0-develop.0d5f1aa1f7",
299
- "@salsita-ai/contracts": "0.50.0-develop.0d5f1aa1f7"
298
+ "@salsita-ai/common": "0.50.0-develop.2331d4a6b7",
299
+ "@salsita-ai/contracts": "0.50.0-develop.2331d4a6b7"
300
300
  },
301
301
  "devDependencies": {
302
302
  "@clerk/clerk-react": "5.61.8",
@@ -1 +0,0 @@
1
- import{browserDetailsTool as e}from"@salsita-ai/contracts/assistant";let t=e=>{let t=[];for(let i=0;i<e.length;i+=64)t.push(e.slice(i,i+64));return t};export const browserDetailsClientTool=e.client(()=>{let e=(()=>{let e=[`User agent: ${navigator.userAgent}`,`Viewport: ${window.innerWidth} x ${window.innerHeight} px`,`Screen: ${window.screen.width} x ${window.screen.height} px`,`Device pixel ratio: ${window.devicePixelRatio}`,`Language: ${navigator.language}`,`Time zone: ${Intl.DateTimeFormat().resolvedOptions().timeZone}`].flatMap(t),i=document.createElement("canvas");i.width=720,i.height=48+26*e.length;let n=i.getContext("2d");if(!n)throw Error("The browser details capture failed because the canvas has no 2D context. Retry in another browser.");return n.fillStyle="#ffffff",n.fillRect(0,0,i.width,i.height),n.fillStyle="#111111",n.font="16px monospace",n.textBaseline="top",e.forEach((e,t)=>n.fillText(e,24,24+26*t)),i.toDataURL("image/png")})();return[{type:"image",source:{type:"data",value:e.slice(e.indexOf(",")+1),mimeType:"image/png"}}]});
@@ -1 +0,0 @@
1
- import{AssistantGraphQLToolInput as e,applyTool as t,queryTool as o}from"@salsita-ai/contracts/assistant";let a=e=>({results:[],committed:!1,message:e}),s=e=>JSON.parse(JSON.stringify(e)),i=t=>{let o=e.safeParse(t);if(!o.success){let e=o.error.issues.map(e=>`${e.path.join(".")}: ${e.message}`).join("; ");return{ok:!1,refusal:a(`Nothing was run, because the tool input is not a list of operations (${e}). Send { "operations": [{ "query": "<one GraphQL operation>" }] } with at least one item.`)}}return{ok:!0,operations:o.data.operations.map(({query:e,variables:t,operationName:o})=>({document:e,variables:t??void 0,operationName:o??void 0}))}};export const queryClientTool=o.client(async(e,{context:t})=>{let o=i(e);return o.ok?t.storeSystem.isInitialized?s({results:(await t.graphQLSystem.prepare(t.storeSystem.stateSlices.committed.value.state,o.operations)).map(({result:e})=>e),committed:!1}):a("Nothing was run, because the configuration is not loaded yet, so there is no state to query. Call the tool again once the configurator has finished loading."):o.refusal});export const applyClientTool=t.client(async(e,{context:t})=>{if("owner"!==t.getActiveMode())return a("Nothing was run, because changes cannot be applied in this session: the user may view this configuration but not change it. The query tool still works here, including mutations, to show what a change would do without keeping it.");let o=i(e);return o.ok?s(await t.graphQLSystem.execute(o.operations)):o.refusal});