{"_id":"@dibbla-agents/sdk-ts","_rev":"2-68eb6f9ded8c324a2cdf7a2acd43a6ed","name":"@dibbla-agents/sdk-ts","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@dibbla-agents/sdk-ts","version":"0.0.1","keywords":["dibbla","agents","workflow","grpc","sdk"],"author":{"name":"Dibbla"},"license":"MIT","_id":"@dibbla-agents/sdk-ts@0.0.1","maintainers":[{"name":"erikknave","email":"erik.knave@gmail.com"}],"dist":{"shasum":"9a32d391b643fa13e170585fce409d69b427e105","tarball":"https://registry.npmjs.org/@dibbla-agents/sdk-ts/-/sdk-ts-0.0.1.tgz","fileCount":57,"integrity":"sha512-WAZBthD0Rttwgbf/4ThxYf9uPdFSTPVW3eRxvBCAK2U9YWbyikf/LGK/Mr/NNHoUkx0BJ+VWZUkgqukuGAaX6Q==","signatures":[{"sig":"MEUCIQDLU+0b/C6d1RQaLc1/14f+c1aKAPtvq7bdd299cqcYcAIgTzBET/eSK1U5RvV+VB3gQJumbp/+BYdJcBXLSQOPpIA=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":360179},"main":"dist/index.js","types":"dist/index.d.ts","engines":{"node":">=18.0.0"},"gitHead":"dfaeceff908fcef6f43d104497c87c051d4b11e6","scripts":{"dev":"tsc --watch","lint":"eslint src --ext .ts","test":"node --test","build":"tsc","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"_npmUser":{"name":"erikknave","email":"erik.knave@gmail.com"},"_npmVersion":"10.9.2","description":"Dibbla Agents SDK for TypeScript - Build workflow functions with gRPC communication","directories":{},"_nodeVersion":"22.14.0","dependencies":{"zod":"^3.22.0","dotenv":"^16.3.0","@grpc/grpc-js":"^1.9.0","murmurhash3js":"^3.0.1","@grpc/proto-loader":"^0.7.0"},"_hasShrinkwrap":false,"devDependencies":{"ts-node":"^10.9.2","typescript":"^5.3.0","@types/node":"^20.0.0","@types/murmurhash3js":"^3.0.3"},"_npmOperationalInternal":{"tmp":"tmp/sdk-ts_0.0.1_1764455724572_0.9024372622336063","host":"s3://npm-registry-packages-npm-production"},"deprecated":"Package no longer supported. Contact Support at https://www.npmjs.com/support for more info."}},"time":{"created":"2025-11-29T22:35:24.471Z","modified":"2026-09-23T18:12:45.520Z","0.0.1":"2025-11-29T22:35:24.776Z"},"author":{"name":"Dibbla"},"license":"MIT","keywords":["dibbla","agents","workflow","grpc","sdk"],"description":"Dibbla Agents SDK for TypeScript - Build workflow functions with gRPC communication","maintainers":[{"name":"erikknave","email":"erik.knave@gmail.com"}],"readme":"# Dibbla SDK for TypeScript\n\nA TypeScript SDK for building workflow functions with gRPC communication and automatic TLS support.\n\n## Architecture\n\nThis SDK mirrors the Go SDK with idiomatic TypeScript patterns:\n\n- **Root Package**: Public API for building workflow functions\n- **Internal**: Private implementation details - gRPC communication, state management, and function infrastructure\n\n## Quick Start\n\n### Prerequisites\n\n- Node.js 18.0.0 or later\n- Access to a gRPC workflow server\n\n### Installation\n\n```bash\nnpm install @dibbla-agents/sdk-ts\n```\n\n### Example Usage\n\nCreate a simple worker with custom functions:\n\n```typescript\nimport * as sdk from '@dibbla-agents/sdk-ts';\nimport { z } from 'zod';\n\n// Define input/output schemas with Zod\nconst GreetingInput = z.object({\n  name: z.string(),\n});\n\nconst GreetingOutput = z.object({\n  message: z.string(),\n});\n\nasync function main() {\n  // Create server with minimal configuration\n  // (defaults to grpc.dibbla.com:443 with TLS enabled)\n  const server = sdk.create({\n    serverName: 'my-custom-worker',\n    serverApiToken: process.env.SERVER_API_TOKEN,\n  });\n\n  // Register a simple function\n  const greetingFn = sdk.newSimpleFunction({\n    name: 'greeting',\n    version: '1.0.0',\n    description: 'Generate a greeting message',\n    input: GreetingInput,\n    output: GreetingOutput,\n    handler: (input) => ({\n      message: `Hello, ${input.name}!`,\n    }),\n    // tags: ['utility', 'greeting'], // Optional - not used in most cases\n  });\n\n  server.registerFunction(greetingFn);\n\n  // Or register multiple functions at once:\n  // server.registerFunctions([greetingFn, otherFn, anotherFn]);\n\n  // Start server (blocks forever)\n  console.log('Starting worker...');\n  await server.start();\n}\n\nmain().catch(console.error);\n```\n\n## Configuration\n\n### Environment Variables\n\n| Variable              | Default               | Description                                    |\n| --------------------- | --------------------- | ---------------------------------------------- |\n| `SERVER_NAME`         | `codex-ts-worker`     | Unique identifier for this worker              |\n| `GRPC_SERVER_ADDRESS` | `grpc.dibbla.com:443` | Address of the workflow server                 |\n| `SERVER_API_TOKEN`    | _(empty)_             | Authentication token                           |\n| `GRPC_USE_TLS`        | _(auto-detect)_       | Enable/disable TLS (`true`, `false`, or empty) |\n\n### TLS Configuration\n\nThe SDK defaults to `grpc.dibbla.com:443` with TLS enabled. It automatically detects when to use TLS based on the server address:\n\n- **Production addresses** (default: `grpc.dibbla.com:443`): TLS enabled with system certificates\n- **Localhost addresses** (`localhost:`, `127.0.0.1:`, `[::1]:`): No TLS (for local development)\n\n#### Explicit TLS Control\n\n```typescript\n// Minimal configuration - uses grpc.dibbla.com:443 with TLS (recommended)\nconst server = sdk.create({\n  serverName: 'my-worker',\n  serverApiToken: 'your-token',\n});\n\n// Local development - uses localhost without TLS\nconst server = sdk.create({\n  serverName: 'my-worker',\n  grpcServerAddress: 'localhost:50051',\n});\n\n// Force TLS on for localhost (advanced)\nconst server = sdk.create({\n  serverName: 'my-worker',\n  grpcServerAddress: 'localhost:9090',\n  useTLS: true,\n});\n```\n\n## Creating Custom Functions\n\nThe SDK provides two types of functions:\n\n### Simple Functions\n\nFor basic input → output transformations:\n\n```typescript\nconst fn = sdk.newSimpleFunction({\n  name: 'my-function',\n  version: '1.0.0',\n  description: 'A simple function',\n  input: MyInputSchema,\n  output: MyOutputSchema,\n  handler: (input) => {\n    // Your logic here\n    return output;\n  },\n  tags: ['tag1', 'tag2'], // Optional - see note below\n});\n```\n\n> **Note on Tags**: The `tags` field is optional and currently not used by most workflow features. It is included for future use cases such as function discovery, filtering, or categorization. You can omit it or leave it as an empty array.\n\n### Advanced Functions\n\nFor functions needing access to workflow context:\n\n```typescript\nconst fn = sdk.newFunction({\n  name: 'my-function',\n  version: '1.0.0',\n  description: 'An advanced function',\n  input: MyInputSchema,\n  output: MyOutputSchema,\n  handler: async (input, event, globalState) => {\n    // Access workflow info: event.workflow, event.node, etc.\n    // Use cache: globalState.cache?.get(key)\n    // Use store: globalState.store?.get(workflowId, key)\n    // Use OAuth: globalState.oauth?.getAccessToken('google', event.run)\n    return output;\n  },\n  cacheTTLMs: 5 * 60 * 1000, // 5 minutes\n});\n```\n\n## Features\n\n### Type-Safe Functions with Zod\n\n- Define input/output schemas using Zod\n- Automatic JSON Schema generation for function registration\n- Runtime validation of inputs and outputs\n- Full TypeScript type inference\n\n### Built-in Caching\n\n- Per-function cache TTL configuration\n- Automatic cache key generation using murmur3 hash\n- gRPC-based distributed cache\n\n### OAuth Access Tokens\n\nThe SDK provides OAuth token management that allows your functions to call external APIs on behalf of users. When a user connects their account (e.g., Google, Microsoft) through the workflow UI, your functions can retrieve valid access tokens to make API calls.\n\n#### How It Works\n\n1. **User connects their account** in the workflow UI (handled by the platform)\n2. **Your function requests a token** using `globalState.oauth?.getAccessToken(provider, runId)`\n3. **The SDK returns a fresh token** (automatically refreshed if expired)\n4. **You use the token** to call external APIs with the user's permissions\n\n#### Basic Usage\n\n```typescript\nconst fn = sdk.newFunction({\n  name: 'call_google_api',\n  version: '1.0.0',\n  description: 'Calls a Google API on behalf of the user',\n  input: MyInputSchema,\n  output: MyOutputSchema,\n  handler: async (input, event, globalState) => {\n    // Get an access token for Google\n    const token = await globalState.oauth?.getAccessToken('google', event.run);\n    \n    if (!token) {\n      throw new Error('Please connect your Google account first');\n    }\n\n    // Use the token to call Google APIs\n    // token.accessToken - the bearer token\n    // token.tokenType   - typically \"Bearer\"\n    // token.expiresAt   - Unix timestamp when token expires\n\n    return output;\n  },\n});\n```\n\n#### Token Response Object\n\n| Property | Type | Description |\n|----------|------|-------------|\n| `accessToken` | `string` | The OAuth bearer token to use in API calls |\n| `tokenType` | `string` | Token type, typically `\"Bearer\"` |\n| `expiresAt` | `number` | Unix timestamp (seconds) when the token expires |\n| `provider` | `string` | The provider name (`\"google\"`, `\"microsoft\"`, `\"github\"`) |\n\n#### Checking Connected Providers\n\nYou can check which providers a user has connected before attempting to get tokens:\n\n```typescript\nconst providers = await globalState.oauth?.getConnectedProviders(event.run);\n// Returns: { google: { connected: true, email: \"user@gmail.com\", ... }, ... }\n\nif (!providers?.google?.connected) {\n  throw new Error('Please connect your Google account to use this feature');\n}\n```\n\n#### Supported Providers\n\n| Provider | API Access |\n|----------|------------|\n| `google` | Gmail, Calendar, Drive, Sheets, Docs, and all Google Workspace APIs |\n| `microsoft` | Outlook, OneDrive, Teams, SharePoint, and Microsoft Graph APIs |\n| `github` | Repositories, Issues, Pull Requests, and GitHub REST/GraphQL APIs |\n\n---\n\n## Tutorial: Building a Google Sheets Integration\n\nThis tutorial walks through building a function that reads data from Google Sheets. It demonstrates OAuth tokens, external API calls, and status messages working together.\n\n### Step 1: Define Your Schemas\n\nStart by defining the input and output schemas with Zod:\n\n```typescript\nimport * as sdk from '@dibbla-agents/sdk-ts';\nimport { z } from 'zod';\n\n// Input: just the Google Sheets URL\nconst ReadSheetsInput = z.object({\n  url: z.string().describe('Google Sheets URL (e.g., https://docs.google.com/spreadsheets/d/1abc.../edit)'),\n});\n\n// Output: the spreadsheet data\nconst ReadSheetsOutput = z.object({\n  title: z.string().describe('Spreadsheet title'),\n  data: z.array(z.array(z.string())).describe('2D array of cell values'),\n  rowCount: z.number().describe('Number of rows'),\n});\n```\n\n### Step 2: Parse the Spreadsheet ID\n\nGoogle Sheets URLs contain a spreadsheet ID that we need to extract:\n\n```typescript\nfunction parseSpreadsheetId(url: string): string {\n  const match = url.match(/\\/spreadsheets\\/d\\/([a-zA-Z0-9-_]+)/);\n  if (!match) {\n    throw new Error('Invalid Google Sheets URL');\n  }\n  return match[1];\n}\n```\n\n### Step 3: Build the Function\n\nNow create the function that ties everything together:\n\n```typescript\nexport const readGoogleSheetsFn = sdk.newFunction({\n  name: 'read_google_sheets',\n  version: '1.0.0',\n  description: 'Read data from a Google Sheets spreadsheet',\n  input: ReadSheetsInput,\n  output: ReadSheetsOutput,\n  handler: async (input, event, state) => {\n    // 1. Send initial status message\n    await state.rpc?.sendStatusEvent(event, 'Connecting to Google Sheets...', {\n      url: input.url,\n    });\n\n    // 2. Check OAuth is available\n    if (!state.oauth) {\n      throw new Error('OAuth not available');\n    }\n\n    // 3. Get the user's Google access token\n    const token = await state.oauth.getAccessToken('google', event.run);\n    \n    // 4. Parse the spreadsheet ID from the URL\n    const spreadsheetId = parseSpreadsheetId(input.url);\n\n    // 5. Send progress update\n    await state.rpc?.sendStatusEvent(event, 'Reading spreadsheet data...', {\n      spreadsheetId,\n    });\n\n    // 6. Call Google Sheets API\n    const apiUrl = `https://sheets.googleapis.com/v4/spreadsheets/${spreadsheetId}?includeGridData=true`;\n    \n    const response = await fetch(apiUrl, {\n      headers: {\n        'Authorization': `Bearer ${token.accessToken}`,\n      },\n    });\n\n    if (!response.ok) {\n      const error = await response.text();\n      throw new Error(`Google Sheets API error: ${error}`);\n    }\n\n    const spreadsheet = await response.json();\n\n    // 7. Extract cell data from the first sheet\n    const sheet = spreadsheet.sheets[0];\n    const rows = sheet.data[0].rowData || [];\n    const data: string[][] = rows.map((row: any) => \n      (row.values || []).map((cell: any) => cell.formattedValue || '')\n    );\n\n    // 8. Send completion status\n    await state.rpc?.sendStatusEvent(event, 'Successfully read spreadsheet', {\n      title: spreadsheet.properties.title,\n      rowCount: data.length,\n    });\n\n    // 9. Return the result\n    return {\n      title: spreadsheet.properties.title,\n      data,\n      rowCount: data.length,\n    };\n  },\n});\n```\n\n### Step 4: Register and Run\n\n```typescript\nasync function main() {\n  const server = sdk.create({\n    serverName: 'sheets-worker',\n    serverApiToken: process.env.SERVER_API_TOKEN,\n  });\n\n  server.registerFunction(readGoogleSheetsFn);\n\n  console.log('Starting Google Sheets worker...');\n  await server.start();\n}\n\nmain().catch(console.error);\n```\n\n### Key Takeaways\n\n1. **Always check for OAuth availability** before attempting to get tokens\n2. **Use status messages** to provide feedback during long operations\n3. **Handle API errors gracefully** with meaningful error messages\n4. **The access token is automatically refreshed** - you don't need to handle token expiration\n\n### Complete Examples\n\nSee the `examples/` directory for full working examples:\n\n- `examples/google-sheets-worker.ts` - Complete Google Sheets read/write\n- `examples/oauth-example-worker.ts` - OAuth token retrieval for multiple providers\n- `examples/ts-sdk-examples-worker.ts` - Modular worker with all functions\n\n---\n\n## Organizing Functions in Modules\n\nFor larger projects, organize your functions into separate files and use a minimal entry point. This keeps your codebase clean and makes it easy to see the overall structure.\n\n### Project Structure\n\n```\nmy-worker/\n├── functions/\n│   ├── index.ts          # Exports all functions\n│   ├── sheets.ts         # Google Sheets functions\n│   ├── email.ts          # Email functions\n│   └── utils.ts          # Utility functions\n├── main.ts               # Minimal entry point\n└── package.json\n```\n\n### Function Module (`functions/sheets.ts`)\n\nEach module exports its function definitions:\n\n```typescript\nimport * as sdk from '@dibbla-agents/sdk-ts';\nimport { z } from 'zod';\n\nconst ReadSheetsInput = z.object({\n  url: z.string(),\n});\n\nconst ReadSheetsOutput = z.object({\n  data: z.array(z.array(z.string())),\n});\n\nexport const readSheetsFn = sdk.newFunction({\n  name: 'read_sheets',\n  version: '1.0.0',\n  description: 'Read from Google Sheets',\n  input: ReadSheetsInput,\n  output: ReadSheetsOutput,\n  handler: async (input, event, state) => {\n    // ... implementation\n  },\n});\n```\n\n### Barrel File (`functions/index.ts`)\n\nRe-export all functions and provide a convenience array:\n\n```typescript\nexport { readSheetsFn, writeSheetsFn } from './sheets';\nexport { sendEmailFn } from './email';\nexport { formatDateFn, parseJsonFn } from './utils';\n\n// Import for the `all` array\nimport { readSheetsFn, writeSheetsFn } from './sheets';\nimport { sendEmailFn } from './email';\nimport { formatDateFn, parseJsonFn } from './utils';\n\n// All functions for bulk registration\nexport const all = [\n  readSheetsFn,\n  writeSheetsFn,\n  sendEmailFn,\n  formatDateFn,\n  parseJsonFn,\n];\n```\n\n### Minimal Entry Point (`main.ts`)\n\nThe entry point becomes remarkably concise:\n\n```typescript\nimport 'dotenv/config';\nimport * as sdk from '@dibbla-agents/sdk-ts';\nimport * as functions from './functions';\n\nasync function main() {\n  const server = sdk.create({\n    serverName: process.env.SERVER_NAME || 'my-worker',\n    serverApiToken: process.env.SERVER_API_TOKEN,\n  });\n\n  // Register all functions at once\n  server.registerFunctions(functions.all);\n\n  console.log(`Starting worker with ${functions.all.length} functions...`);\n  await server.start();\n}\n\nmain().catch(console.error);\n```\n\n### Benefits\n\n- **Separation of concerns** - Each domain has its own file\n- **Easy to navigate** - The entry point shows the full picture\n- **Testable** - Functions can be unit tested in isolation\n- **Scalable** - Add new functions without touching the entry point\n\n---\n\n### Status Messages\n\nSend real-time status updates to the workflow UI during function execution. This is useful for long-running tasks to provide progress feedback to users.\n\n#### How It Works\n\nStatus messages are sent via the gRPC stream as `status_message` events. They are routed using the correlation ID from the original function request, allowing the workflow server to associate the status update with the correct execution context.\n\nThe status message contains:\n- **text**: A human-readable message displayed in the UI\n- **payload**: Optional structured JSON data for detailed progress information\n\n#### Usage\n\n```typescript\nconst fn = sdk.newFunction({\n  name: 'process_data',\n  version: '1.0.0',\n  description: 'Process data with progress updates',\n  input: MyInputSchema,\n  output: MyOutputSchema,\n  handler: async (input, event, globalState) => {\n    // Send initial status\n    await globalState.rpc?.sendStatusEvent(event, 'Starting data processing...', {\n      progress: 0,\n    });\n\n    // ... do some work ...\n\n    // Send progress update with optional payload\n    await globalState.rpc?.sendStatusEvent(event, 'Processing 50% complete', {\n      progress: 50,\n      itemsProcessed: 500,\n    });\n\n    // ... do more work ...\n\n    // Send completion status\n    await globalState.rpc?.sendStatusEvent(event, 'Processing complete!', {\n      progress: 100,\n      totalItems: 1000,\n    });\n\n    return output;\n  },\n});\n```\n\n#### Method Signature\n\n```typescript\nawait globalState.rpc?.sendStatusEvent(event, text, payload?);\n```\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `event` | `EventMessage` | The event message from the handler (required for routing via correlation ID) |\n| `text` | `string` | A human-readable status message |\n| `payload` | `unknown` | Optional JSON-serializable data for structured progress info |\n\n> **Note**: Status messages are fire-and-forget; the function does not wait for acknowledgment. If the gRPC connection is lost, the status message may not be delivered.\n\n### Key-Value Store\n\nStore and retrieve data associated with workflows:\n\n```typescript\n// Get a value\nconst value = await globalState.store?.getString(event.workflow, 'my-key');\n\n// Set a value\nawait globalState.store?.setString(event.workflow, 'my-key', 'my-value');\n```\n\n### Robust Connection Management\n\n- Automatic reconnection on failure\n- Configurable health checks\n- Ping/pong keep-alive mechanism\n- Connection state monitoring\n\n### Function Tags\n\nFunctions can include an optional `tags` array for categorization:\n\n```typescript\nconst fn = sdk.newSimpleFunction({\n  // ...\n  tags: ['utility', 'math'],\n});\n```\n\n> **Note**: Tags are currently not used by most workflow features. They are sent to the workflow server during function registration but have no effect on routing or execution. They are reserved for future use cases such as:\n> - Function discovery and search\n> - UI filtering and grouping\n> - Access control policies\n>\n> You can safely omit the `tags` field or leave it as an empty array.\n\n## Troubleshooting\n\n### Common Issues\n\n**Connection Failures**: \n- By default, the SDK connects to `grpc.dibbla.com:443` with TLS enabled\n- For local development, set `GRPC_SERVER_ADDRESS=localhost:50051`\n- Verify your server address points to a running workflow server\n- Check TLS configuration matches your server setup\n\n**Authentication Errors**: \n- Ensure `SERVER_API_TOKEN` is set if the server requires authentication\n- Check token is valid and not expired\n\n**TLS Certificate Errors**:\n- Ensure system CA certificates are up to date\n- For self-signed certificates, you may need to disable TLS verification (not recommended for production)\n\n### Debug Mode\n\nEnable verbose logging by examining console output. The SDK logs all connection attempts, event messages, and errors.\n\n## License\n\nPart of the Dibbla project.\n\n","readmeFilename":"README.md"}