@nxcode/sdk 1.0.1 → 1.0.12

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.
@@ -406,6 +406,46 @@ class NxcodePayment {
406
406
  * Provides access to AI capabilities through the Nxcode AI Gateway.
407
407
  * All AI calls require user authentication and are billed to the user's balance.
408
408
  */
409
+ /**
410
+ * Convert simplified schema to JSON Schema (Gemini format)
411
+ */
412
+ function toJsonSchema(schema) {
413
+ // Primitive types
414
+ if (schema === 'string') {
415
+ return { type: 'STRING' };
416
+ }
417
+ if (schema === 'number') {
418
+ return { type: 'NUMBER' };
419
+ }
420
+ if (schema === 'boolean') {
421
+ return { type: 'BOOLEAN' };
422
+ }
423
+ // Array type
424
+ if (Array.isArray(schema)) {
425
+ if (schema.length !== 1) {
426
+ throw new Error('Array schema must have exactly one element type');
427
+ }
428
+ return {
429
+ type: 'ARRAY',
430
+ items: toJsonSchema(schema[0]),
431
+ };
432
+ }
433
+ // Object type
434
+ if (typeof schema === 'object' && schema !== null) {
435
+ const properties = {};
436
+ const required = [];
437
+ for (const [key, value] of Object.entries(schema)) {
438
+ properties[key] = toJsonSchema(value);
439
+ required.push(key); // All fields required by default
440
+ }
441
+ return {
442
+ type: 'OBJECT',
443
+ properties,
444
+ required,
445
+ };
446
+ }
447
+ throw new Error(`Invalid schema type: ${schema}`);
448
+ }
409
449
  class NxcodeAI {
410
450
  constructor(apiEndpoint, appId, getToken) {
411
451
  this.apiEndpoint = apiEndpoint;
@@ -433,6 +473,13 @@ class NxcodeAI {
433
473
  const geminiRequest = {
434
474
  contents: this.convertMessagesToGemini(options.messages),
435
475
  };
476
+ // Add structured output config if schema provided
477
+ if (options.responseSchema) {
478
+ geminiRequest.generationConfig = {
479
+ responseMimeType: 'application/json',
480
+ responseSchema: toJsonSchema(options.responseSchema),
481
+ };
482
+ }
436
483
  const response = await fetch(`${this.apiEndpoint}/api/ai-gateway/v1beta/models/${model}:generateContent`, {
437
484
  method: 'POST',
438
485
  headers: this.getHeaders(token),
@@ -457,6 +504,7 @@ class NxcodeAI {
457
504
  const result = await this.chat({
458
505
  messages: [{ role: 'user', content: options.prompt }],
459
506
  model: options.model,
507
+ responseSchema: options.responseSchema,
460
508
  });
461
509
  return {
462
510
  text: result.content,
@@ -659,6 +707,272 @@ class NxcodeAI {
659
707
  }
660
708
  }
661
709
 
710
+ /**
711
+ * Nxcode SDK - Agent Module
712
+ *
713
+ * Provides AI agents with tool calling capabilities.
714
+ * Agents can call backend endpoints as tools and maintain conversation history.
715
+ */
716
+ // ==================== Helper Functions ====================
717
+ /**
718
+ * Convert simple schema to JSON Schema format for Gemini
719
+ */
720
+ function toJsonSchemaProperty(schema) {
721
+ if (schema === 'string')
722
+ return { type: 'STRING' };
723
+ if (schema === 'number')
724
+ return { type: 'NUMBER' };
725
+ if (schema === 'boolean')
726
+ return { type: 'BOOLEAN' };
727
+ if (Array.isArray(schema)) {
728
+ return {
729
+ type: 'ARRAY',
730
+ items: toJsonSchemaProperty(schema[0]),
731
+ };
732
+ }
733
+ if (typeof schema === 'object') {
734
+ const properties = {};
735
+ const required = [];
736
+ for (const [key, value] of Object.entries(schema)) {
737
+ properties[key] = toJsonSchemaProperty(value);
738
+ required.push(key);
739
+ }
740
+ return { type: 'OBJECT', properties, required };
741
+ }
742
+ return { type: 'STRING' };
743
+ }
744
+ /**
745
+ * Convert AgentTool to Gemini function declaration
746
+ */
747
+ function toolToGeminiFunction(tool) {
748
+ const declaration = {
749
+ name: tool.name,
750
+ description: tool.description,
751
+ };
752
+ // Always include parameters object (even if empty) for Gemini compatibility
753
+ const properties = {};
754
+ const required = [];
755
+ if (tool.parameters) {
756
+ for (const [key, value] of Object.entries(tool.parameters)) {
757
+ properties[key] = toJsonSchemaProperty(value);
758
+ required.push(key);
759
+ }
760
+ }
761
+ declaration.parameters = {
762
+ type: 'OBJECT',
763
+ properties,
764
+ required,
765
+ };
766
+ return declaration;
767
+ }
768
+ // ==================== Agent Class ====================
769
+ class Agent {
770
+ constructor(options, apiEndpoint, appId, getToken) {
771
+ this.history = [];
772
+ this.options = {
773
+ instructions: options.instructions,
774
+ tools: options.tools,
775
+ model: options.model || 'fast',
776
+ maxSteps: options.maxSteps || 10,
777
+ };
778
+ this.apiEndpoint = apiEndpoint;
779
+ this.appId = appId;
780
+ this.getToken = getToken;
781
+ }
782
+ /**
783
+ * Run the agent with user input
784
+ */
785
+ async run(input, callbacks) {
786
+ const token = this.getToken();
787
+ if (!token) {
788
+ throw new Error('Not authenticated. Please login first.');
789
+ }
790
+ // Add user message to history
791
+ this.history.push({ role: 'user', content: input });
792
+ const toolCalls = [];
793
+ let totalInputTokens = 0;
794
+ let totalOutputTokens = 0;
795
+ let steps = 0;
796
+ // Agent loop
797
+ while (steps < this.options.maxSteps) {
798
+ steps++;
799
+ console.log(`[Agent] Step ${steps}/${this.options.maxSteps}`);
800
+ // Call Gemini with tools
801
+ const response = await this.callGemini(token);
802
+ console.log(`[Agent] Response:`, response.functionCall ? `function call: ${response.functionCall.name}` : `text: ${response.text?.substring(0, 50)}`);
803
+ totalInputTokens += response.usage?.inputTokens || 0;
804
+ totalOutputTokens += response.usage?.outputTokens || 0;
805
+ // Check if AI wants to call a function
806
+ const functionCall = response.functionCall;
807
+ if (functionCall) {
808
+ callbacks?.onToolCall?.(functionCall.name, functionCall.args);
809
+ // Find the tool
810
+ const tool = this.options.tools.find((t) => t.name === functionCall.name);
811
+ if (!tool) {
812
+ throw new Error(`Unknown tool: ${functionCall.name}`);
813
+ }
814
+ // Call the tool endpoint
815
+ const result = await this.callTool(tool, functionCall.args, token);
816
+ callbacks?.onToolResult?.(functionCall.name, result);
817
+ // Record tool call
818
+ toolCalls.push({
819
+ name: functionCall.name,
820
+ params: functionCall.args,
821
+ result,
822
+ });
823
+ // Add function call and response to history using raw parts from Gemini
824
+ this.history.push({
825
+ role: 'model',
826
+ content: '',
827
+ parts: response.rawParts || [{ functionCall: { name: functionCall.name, args: functionCall.args } }],
828
+ });
829
+ this.history.push({
830
+ role: 'user',
831
+ content: '',
832
+ parts: [{ functionResponse: { name: functionCall.name, response: result } }],
833
+ });
834
+ // Continue loop to let AI process the result
835
+ console.log(`[Agent] Continuing loop after tool call...`);
836
+ continue;
837
+ }
838
+ // No function call - AI is done, return the text response
839
+ const output = response.text || '';
840
+ // Add assistant response to history
841
+ this.history.push({ role: 'assistant', content: output });
842
+ return {
843
+ output,
844
+ toolCalls,
845
+ usage: {
846
+ inputTokens: totalInputTokens,
847
+ outputTokens: totalOutputTokens,
848
+ },
849
+ };
850
+ }
851
+ throw new Error(`Agent exceeded maximum steps (${this.options.maxSteps})`);
852
+ }
853
+ /**
854
+ * Clear conversation history
855
+ */
856
+ reset() {
857
+ this.history = [];
858
+ }
859
+ /**
860
+ * Get current conversation history
861
+ */
862
+ getHistory() {
863
+ return [...this.history];
864
+ }
865
+ // ==================== Private Methods ====================
866
+ async callGemini(token) {
867
+ // Build contents from history
868
+ const contents = this.history.map((msg) => {
869
+ if (msg.parts) {
870
+ return {
871
+ role: msg.role === 'assistant' ? 'model' : msg.role,
872
+ parts: msg.parts,
873
+ };
874
+ }
875
+ return {
876
+ role: msg.role === 'assistant' ? 'model' : 'user',
877
+ parts: [{ text: msg.content }],
878
+ };
879
+ });
880
+ // Build tools
881
+ const tools = [
882
+ {
883
+ functionDeclarations: this.options.tools.map(toolToGeminiFunction),
884
+ },
885
+ ];
886
+ // Build request with tool config to encourage tool use
887
+ const request = {
888
+ contents,
889
+ tools,
890
+ toolConfig: {
891
+ functionCallingConfig: {
892
+ mode: 'AUTO', // Let model decide when to use tools
893
+ },
894
+ },
895
+ systemInstruction: {
896
+ parts: [{ text: this.options.instructions }],
897
+ },
898
+ };
899
+ console.log('[Agent] Sending request:', JSON.stringify(request, null, 2));
900
+ const response = await fetch(`${this.apiEndpoint}/api/ai-gateway/v1beta/models/${this.options.model}:generateContent`, {
901
+ method: 'POST',
902
+ headers: {
903
+ 'Content-Type': 'application/json',
904
+ 'X-App-Id': this.appId,
905
+ Authorization: `Bearer ${token}`,
906
+ },
907
+ body: JSON.stringify(request),
908
+ });
909
+ if (!response.ok) {
910
+ const error = await response.json().catch(() => ({}));
911
+ throw new Error(error.detail || error.message || 'Agent API call failed');
912
+ }
913
+ const data = await response.json();
914
+ // Parse response
915
+ const candidate = data.candidates?.[0];
916
+ const parts = candidate?.content?.parts || [];
917
+ // Check for function call (support both camelCase and snake_case)
918
+ for (const part of parts) {
919
+ const funcCall = part.functionCall || part.function_call;
920
+ if (funcCall) {
921
+ return {
922
+ functionCall: {
923
+ name: funcCall.name,
924
+ args: funcCall.args || {},
925
+ },
926
+ rawParts: parts, // Save raw parts including thought_signature
927
+ usage: {
928
+ inputTokens: data.usageMetadata?.promptTokenCount || 0,
929
+ outputTokens: data.usageMetadata?.candidatesTokenCount || 0,
930
+ },
931
+ };
932
+ }
933
+ }
934
+ // Return text response
935
+ const text = parts.find((p) => p.text)?.text || '';
936
+ return {
937
+ text,
938
+ usage: {
939
+ inputTokens: data.usageMetadata?.promptTokenCount || 0,
940
+ outputTokens: data.usageMetadata?.candidatesTokenCount || 0,
941
+ },
942
+ };
943
+ }
944
+ async callTool(tool, params, token) {
945
+ // Build context to pass to tool
946
+ const body = {
947
+ ...params,
948
+ _context: {
949
+ agentId: this.options.instructions.slice(0, 20), // Simple ID
950
+ stepIndex: this.history.length,
951
+ previousTools: this.history
952
+ .filter((m) => m.parts?.some((p) => p.functionResponse))
953
+ .map((m) => {
954
+ const fr = m.parts?.find((p) => p.functionResponse)?.functionResponse;
955
+ return fr ? { name: fr.name, result: fr.response } : null;
956
+ })
957
+ .filter(Boolean),
958
+ },
959
+ };
960
+ const response = await fetch(tool.endpoint, {
961
+ method: 'POST',
962
+ headers: {
963
+ 'Content-Type': 'application/json',
964
+ Authorization: `Bearer ${token}`,
965
+ },
966
+ body: JSON.stringify(body),
967
+ });
968
+ if (!response.ok) {
969
+ const error = await response.text();
970
+ return { error: `Tool call failed: ${error}` };
971
+ }
972
+ return response.json();
973
+ }
974
+ }
975
+
662
976
  /**
663
977
  * Nxcode SDK
664
978
  *
@@ -923,6 +1237,37 @@ class NxcodeSDK {
923
1237
  await self.ensureInitialized();
924
1238
  return self._ai.generateStream(options);
925
1239
  },
1240
+ /**
1241
+ * Create an AI agent with tool calling capabilities
1242
+ *
1243
+ * @example
1244
+ * const agent = Nxcode.ai.createAgent({
1245
+ * instructions: 'You are a helpful assistant that can search and send emails.',
1246
+ * tools: [
1247
+ * {
1248
+ * name: 'search_emails',
1249
+ * description: 'Search user emails',
1250
+ * parameters: { query: 'string' },
1251
+ * endpoint: '/api/emails/search'
1252
+ * },
1253
+ * {
1254
+ * name: 'send_email',
1255
+ * description: 'Send an email',
1256
+ * parameters: { to: 'string', subject: 'string', body: 'string' },
1257
+ * endpoint: '/api/emails/send'
1258
+ * }
1259
+ * ]
1260
+ * });
1261
+ *
1262
+ * const result = await agent.run('Find emails from John and reply to him');
1263
+ * console.log(result.output);
1264
+ */
1265
+ createAgent(options) {
1266
+ if (!self.config) {
1267
+ throw new Error('Nxcode SDK not configured. Call Nxcode.configure(appId) first.');
1268
+ }
1269
+ return new Agent(options, self.apiEndpoint, self.config.appId, () => self._auth?.getToken() || null);
1270
+ },
926
1271
  };
927
1272
  }
928
1273
  // ==================== Utility Methods ====================
@@ -952,5 +1297,5 @@ if (typeof window !== 'undefined') {
952
1297
  window.Nxcode = Nxcode;
953
1298
  }
954
1299
 
955
- export { Nxcode, Nxcode as default };
1300
+ export { Agent, Nxcode, Nxcode as default };
956
1301
  //# sourceMappingURL=nxcode.esm.js.map