@nxcode/sdk 1.0.5 → 1.0.13

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/nxcode.js CHANGED
@@ -471,9 +471,8 @@
471
471
  */
472
472
  async chat(options) {
473
473
  const token = this.getToken();
474
- if (!token) {
475
- throw new Error('Not authenticated. Please login first.');
476
- }
474
+ // Allow anonymous requests for dev preview and apps with anonymous AI enabled
475
+ const headers = token ? this.getHeaders(token) : this.getAnonymousHeaders();
477
476
  const model = options.model || 'fast';
478
477
  // Build Gemini API request
479
478
  const geminiRequest = {
@@ -488,7 +487,7 @@
488
487
  }
489
488
  const response = await fetch(`${this.apiEndpoint}/api/ai-gateway/v1beta/models/${model}:generateContent`, {
490
489
  method: 'POST',
491
- headers: this.getHeaders(token),
490
+ headers,
492
491
  body: JSON.stringify(geminiRequest),
493
492
  });
494
493
  if (!response.ok) {
@@ -695,24 +694,286 @@
695
694
  };
696
695
  }
697
696
  async handleError(response) {
698
- if (response.status === 401) {
699
- throw new Error('Session expired. Please login again.');
700
- }
701
- if (response.status === 402) {
702
- throw new Error('Insufficient balance. Please top up to continue.');
703
- }
704
697
  let message = 'AI request failed';
705
698
  try {
706
699
  const data = await response.json();
707
700
  message = data.detail || data.message || message;
708
701
  }
709
702
  catch {
710
- // Ignore parse errors
703
+ // Ignore parse errors, use default message based on status
704
+ if (response.status === 401) {
705
+ message = 'Session expired. Please login again.';
706
+ }
707
+ else if (response.status === 402) {
708
+ message = 'Insufficient balance. Please top up to continue.';
709
+ }
711
710
  }
712
711
  throw new Error(message);
713
712
  }
714
713
  }
715
714
 
715
+ /**
716
+ * Nxcode SDK - Agent Module
717
+ *
718
+ * Provides AI agents with tool calling capabilities.
719
+ * Agents can call backend endpoints as tools and maintain conversation history.
720
+ */
721
+ // ==================== Helper Functions ====================
722
+ /**
723
+ * Convert simple schema to JSON Schema format for Gemini
724
+ */
725
+ function toJsonSchemaProperty(schema) {
726
+ if (schema === 'string')
727
+ return { type: 'STRING' };
728
+ if (schema === 'number')
729
+ return { type: 'NUMBER' };
730
+ if (schema === 'boolean')
731
+ return { type: 'BOOLEAN' };
732
+ if (Array.isArray(schema)) {
733
+ return {
734
+ type: 'ARRAY',
735
+ items: toJsonSchemaProperty(schema[0]),
736
+ };
737
+ }
738
+ if (typeof schema === 'object') {
739
+ const properties = {};
740
+ const required = [];
741
+ for (const [key, value] of Object.entries(schema)) {
742
+ properties[key] = toJsonSchemaProperty(value);
743
+ required.push(key);
744
+ }
745
+ return { type: 'OBJECT', properties, required };
746
+ }
747
+ return { type: 'STRING' };
748
+ }
749
+ /**
750
+ * Convert AgentTool to Gemini function declaration
751
+ */
752
+ function toolToGeminiFunction(tool) {
753
+ const declaration = {
754
+ name: tool.name,
755
+ description: tool.description,
756
+ };
757
+ // Always include parameters object (even if empty) for Gemini compatibility
758
+ const properties = {};
759
+ const required = [];
760
+ if (tool.parameters) {
761
+ for (const [key, value] of Object.entries(tool.parameters)) {
762
+ properties[key] = toJsonSchemaProperty(value);
763
+ required.push(key);
764
+ }
765
+ }
766
+ declaration.parameters = {
767
+ type: 'OBJECT',
768
+ properties,
769
+ required,
770
+ };
771
+ return declaration;
772
+ }
773
+ // ==================== Agent Class ====================
774
+ class Agent {
775
+ constructor(options, apiEndpoint, appId, getToken) {
776
+ this.history = [];
777
+ this.options = {
778
+ instructions: options.instructions,
779
+ tools: options.tools,
780
+ model: options.model || 'fast',
781
+ maxSteps: options.maxSteps || 10,
782
+ };
783
+ this.apiEndpoint = apiEndpoint;
784
+ this.appId = appId;
785
+ this.getToken = getToken;
786
+ }
787
+ /**
788
+ * Run the agent with user input
789
+ */
790
+ async run(input, callbacks) {
791
+ const token = this.getToken();
792
+ if (!token) {
793
+ throw new Error('Not authenticated. Please login first.');
794
+ }
795
+ // Add user message to history
796
+ this.history.push({ role: 'user', content: input });
797
+ const toolCalls = [];
798
+ let totalInputTokens = 0;
799
+ let totalOutputTokens = 0;
800
+ let steps = 0;
801
+ // Agent loop
802
+ while (steps < this.options.maxSteps) {
803
+ steps++;
804
+ // Call Gemini with tools
805
+ const response = await this.callGemini(token);
806
+ totalInputTokens += response.usage?.inputTokens || 0;
807
+ totalOutputTokens += response.usage?.outputTokens || 0;
808
+ // Check if AI wants to call a function
809
+ const functionCall = response.functionCall;
810
+ if (functionCall) {
811
+ callbacks?.onToolCall?.(functionCall.name, functionCall.args);
812
+ // Find the tool
813
+ const tool = this.options.tools.find((t) => t.name === functionCall.name);
814
+ if (!tool) {
815
+ throw new Error(`Unknown tool: ${functionCall.name}`);
816
+ }
817
+ // Call the tool endpoint
818
+ const result = await this.callTool(tool, functionCall.args, token);
819
+ callbacks?.onToolResult?.(functionCall.name, result);
820
+ // Record tool call
821
+ toolCalls.push({
822
+ name: functionCall.name,
823
+ params: functionCall.args,
824
+ result,
825
+ });
826
+ // Add function call and response to history using raw parts from Gemini
827
+ this.history.push({
828
+ role: 'model',
829
+ content: '',
830
+ parts: response.rawParts || [{ functionCall: { name: functionCall.name, args: functionCall.args } }],
831
+ });
832
+ this.history.push({
833
+ role: 'user',
834
+ content: '',
835
+ parts: [{ functionResponse: { name: functionCall.name, response: result } }],
836
+ });
837
+ // Continue loop to let AI process the result
838
+ continue;
839
+ }
840
+ // No function call - AI is done, return the text response
841
+ const output = response.text || '';
842
+ // Add assistant response to history
843
+ this.history.push({ role: 'assistant', content: output });
844
+ return {
845
+ output,
846
+ toolCalls,
847
+ usage: {
848
+ inputTokens: totalInputTokens,
849
+ outputTokens: totalOutputTokens,
850
+ },
851
+ };
852
+ }
853
+ throw new Error(`Agent exceeded maximum steps (${this.options.maxSteps})`);
854
+ }
855
+ /**
856
+ * Clear conversation history
857
+ */
858
+ reset() {
859
+ this.history = [];
860
+ }
861
+ /**
862
+ * Get current conversation history
863
+ */
864
+ getHistory() {
865
+ return [...this.history];
866
+ }
867
+ // ==================== Private Methods ====================
868
+ async callGemini(token) {
869
+ // Build contents from history
870
+ const contents = this.history.map((msg) => {
871
+ if (msg.parts) {
872
+ return {
873
+ role: msg.role === 'assistant' ? 'model' : msg.role,
874
+ parts: msg.parts,
875
+ };
876
+ }
877
+ return {
878
+ role: msg.role === 'assistant' ? 'model' : 'user',
879
+ parts: [{ text: msg.content }],
880
+ };
881
+ });
882
+ // Build tools
883
+ const tools = [
884
+ {
885
+ functionDeclarations: this.options.tools.map(toolToGeminiFunction),
886
+ },
887
+ ];
888
+ // Build request with tool config to encourage tool use
889
+ const request = {
890
+ contents,
891
+ tools,
892
+ toolConfig: {
893
+ functionCallingConfig: {
894
+ mode: 'AUTO', // Let model decide when to use tools
895
+ },
896
+ },
897
+ systemInstruction: {
898
+ parts: [{ text: this.options.instructions }],
899
+ },
900
+ };
901
+ const response = await fetch(`${this.apiEndpoint}/api/ai-gateway/v1beta/models/${this.options.model}:generateContent`, {
902
+ method: 'POST',
903
+ headers: {
904
+ 'Content-Type': 'application/json',
905
+ 'X-App-Id': this.appId,
906
+ Authorization: `Bearer ${token}`,
907
+ },
908
+ body: JSON.stringify(request),
909
+ });
910
+ if (!response.ok) {
911
+ const error = await response.json().catch(() => ({}));
912
+ throw new Error(error.detail || error.message || 'Agent API call failed');
913
+ }
914
+ const data = await response.json();
915
+ // Parse response
916
+ const candidate = data.candidates?.[0];
917
+ const parts = candidate?.content?.parts || [];
918
+ // Check for function call (support both camelCase and snake_case)
919
+ for (const part of parts) {
920
+ const funcCall = part.functionCall || part.function_call;
921
+ if (funcCall) {
922
+ return {
923
+ functionCall: {
924
+ name: funcCall.name,
925
+ args: funcCall.args || {},
926
+ },
927
+ rawParts: parts, // Save raw parts including thought_signature
928
+ usage: {
929
+ inputTokens: data.usageMetadata?.promptTokenCount || 0,
930
+ outputTokens: data.usageMetadata?.candidatesTokenCount || 0,
931
+ },
932
+ };
933
+ }
934
+ }
935
+ // Return text response
936
+ const text = parts.find((p) => p.text)?.text || '';
937
+ return {
938
+ text,
939
+ usage: {
940
+ inputTokens: data.usageMetadata?.promptTokenCount || 0,
941
+ outputTokens: data.usageMetadata?.candidatesTokenCount || 0,
942
+ },
943
+ };
944
+ }
945
+ async callTool(tool, params, token) {
946
+ // Build context to pass to tool
947
+ const body = {
948
+ ...params,
949
+ _context: {
950
+ agentId: this.options.instructions.slice(0, 20), // Simple ID
951
+ stepIndex: this.history.length,
952
+ previousTools: this.history
953
+ .filter((m) => m.parts?.some((p) => p.functionResponse))
954
+ .map((m) => {
955
+ const fr = m.parts?.find((p) => p.functionResponse)?.functionResponse;
956
+ return fr ? { name: fr.name, result: fr.response } : null;
957
+ })
958
+ .filter(Boolean),
959
+ },
960
+ };
961
+ const response = await fetch(tool.endpoint, {
962
+ method: 'POST',
963
+ headers: {
964
+ 'Content-Type': 'application/json',
965
+ Authorization: `Bearer ${token}`,
966
+ },
967
+ body: JSON.stringify(body),
968
+ });
969
+ if (!response.ok) {
970
+ const error = await response.text();
971
+ return { error: `Tool call failed: ${error}` };
972
+ }
973
+ return response.json();
974
+ }
975
+ }
976
+
716
977
  /**
717
978
  * Nxcode SDK
718
979
  *
@@ -977,6 +1238,37 @@
977
1238
  await self.ensureInitialized();
978
1239
  return self._ai.generateStream(options);
979
1240
  },
1241
+ /**
1242
+ * Create an AI agent with tool calling capabilities
1243
+ *
1244
+ * @example
1245
+ * const agent = Nxcode.ai.createAgent({
1246
+ * instructions: 'You are a helpful assistant that can search and send emails.',
1247
+ * tools: [
1248
+ * {
1249
+ * name: 'search_emails',
1250
+ * description: 'Search user emails',
1251
+ * parameters: { query: 'string' },
1252
+ * endpoint: '/api/emails/search'
1253
+ * },
1254
+ * {
1255
+ * name: 'send_email',
1256
+ * description: 'Send an email',
1257
+ * parameters: { to: 'string', subject: 'string', body: 'string' },
1258
+ * endpoint: '/api/emails/send'
1259
+ * }
1260
+ * ]
1261
+ * });
1262
+ *
1263
+ * const result = await agent.run('Find emails from John and reply to him');
1264
+ * console.log(result.output);
1265
+ */
1266
+ createAgent(options) {
1267
+ if (!self.config) {
1268
+ throw new Error('Nxcode SDK not configured. Call Nxcode.configure(appId) first.');
1269
+ }
1270
+ return new Agent(options, self.apiEndpoint, self.config.appId, () => self._auth?.getToken() || null);
1271
+ },
980
1272
  };
981
1273
  }
982
1274
  // ==================== Utility Methods ====================
@@ -1006,6 +1298,7 @@
1006
1298
  window.Nxcode = Nxcode;
1007
1299
  }
1008
1300
 
1301
+ exports.Agent = Agent;
1009
1302
  exports.Nxcode = Nxcode;
1010
1303
  exports.default = Nxcode;
1011
1304