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