# Ask Client SDK

Browser-compatible JavaScript/TypeScript client for the Ask API. Use this SDK to integrate Ask's multi-LLM capabilities into your web applications.

## Features

- ✅ **Browser-compatible** - Works in all modern browsers
- ✅ **TypeScript support** - Full type definitions included
- ✅ **Streaming responses** - Real-time text streaming
- ✅ **11 AI providers** - OpenAI, Anthropic, Google, Groq, Mistral, and more
- ✅ **560+ models** - Access hundreds of models through one API
- ✅ **Simple API** - Easy to use, hard to misuse
- ✅ **Zero dependencies** - Uses native fetch and streams

## Installation

### NPM/Yarn/Bun

```bash
npm install @livx.cc/ask
# or
yarn add @livx.cc/ask
# or
bun add @livx.cc/ask
```

### CDN (Browser)

**ESM Module (Recommended)**:
```html
<script type="module">
  import { AskClient } from 'https://cdn.jsdelivr.net/npm/@livx.cc/ask@latest/dist/ask-client.esm.min.js';
  
  const client = new AskClient({
    baseUrl: 'https://your-worker.workers.dev/v1',
    askApiKey: 'ask_abc123...'
  });
</script>
```

**IIFE / Script Tag**:
```html
<script src="https://cdn.jsdelivr.net/npm/@livx.cc/ask@latest/dist/ask-client.iife.min.js"></script>
<script>
  const { AskClient } = AskSDK;
  const client = new AskClient({ /* ... */ });
</script>
```

📖 **Full CDN Guide**: [CDN Usage Documentation](../../docs/CDN_USAGE.md)

## Quick Start

### Basic Usage

```typescript
import { AskClient } from '@livx.cc/ask/client';

// Initialize client
const client = new AskClient({
  baseUrl: 'https://your-worker.workers.dev/v1',
  askApiKey: 'ask_abc123...',
  defaultProvider: 'openai',
  defaultModel: 'gpt-4'
});

// Non-streaming request
const response = await client.completion({
  messages: [
    { role: 'user', content: 'What is the capital of France?' }
  ]
});

console.log(response.content); // "The capital of France is Paris."
```

### Streaming Responses

```typescript
// Streaming request
const stream = await client.streamCompletion({
  messages: [
    { role: 'user', content: 'Tell me a story about a robot' }
  ]
});

// Iterate over chunks
for await (const chunk of stream) {
  process.stdout.write(chunk); // Print as it streams
}
```

### In Browser with DOM

```html
<!DOCTYPE html>
<html>
<head>
  <title>Ask Client Demo</title>
</head>
<body>
  <div id="output"></div>
  <button id="askBtn">Ask AI</button>

  <script type="module">
    import { AskClient } from './path/to/client/index.js';

    const client = new AskClient({
      baseUrl: 'https://your-worker.workers.dev/v1',
      askApiKey: 'ask_abc123...'
    });

    document.getElementById('askBtn').addEventListener('click', async () => {
      const output = document.getElementById('output');
      output.textContent = '';

      const stream = await client.streamCompletion({
        messages: [
          { role: 'user', content: 'Write a haiku about coding' }
        ],
        config: {
          model: 'gpt-4',
          provider: 'openai'
        }
      });

      for await (const chunk of stream) {
        output.textContent += chunk;
      }
    });
  </script>
</body>
</html>
```

## Configuration

### Client Options

```typescript
interface IAskClientOptions {
  /** Base URL of the Ask API (required) */
  baseUrl: string;
  
  /** Ask API key for authentication (recommended) */
  askApiKey?: string;
  
  /** Default provider to use */
  defaultProvider?: string;
  
  /** Default model to use */
  defaultModel?: string;
  
  /** Provider-specific API keys (alternative to askApiKey) */
  providerKeys?: {
    openai?: string;
    anthropic?: string;
    groq?: string;
    google?: string;
    mistral?: string;
    openrouter?: string;
    cohere?: string;
    xai?: string;
    deepseek?: string;
    ai21?: string;
    cloudflare?: string;
  };
  
  /** Cloudflare account ID (required if using Cloudflare provider) */
  cloudflareAccountId?: string;
  
  /** Custom headers to include in all requests */
  headers?: Record<string, string>;
  
  /** Enable debug logging */
  debug?: boolean;
}
```

### Request Configuration

```typescript
interface IAskConfig {
  /** AI provider (e.g., 'openai', 'anthropic', 'groq') */
  provider?: string;
  
  /** Model name (e.g., 'gpt-4', 'claude-3-5-sonnet-latest') */
  model?: string;
  
  /** System prompt to set context */
  systemPrompt?: string;
  
  /** Sampling temperature (0-2, default: 0.7) */
  temperature?: number;
  
  /** Maximum tokens in response */
  maxTokens?: number;
  
  /** Top P sampling parameter */
  topP?: number;
  
  /** Frequency penalty (-2 to 2) */
  frequencyPenalty?: number;
  
  /** Presence penalty (-2 to 2) */
  presencePenalty?: number;
  
  /** User identifier for tracking */
  user?: string;
}
```

## Examples

### Using Different Providers

```typescript
// OpenAI
const openaiResponse = await client.completion({
  messages: [{ role: 'user', content: 'Hello!' }],
  config: { provider: 'openai', model: 'gpt-4' }
});

// Anthropic
const claudeResponse = await client.completion({
  messages: [{ role: 'user', content: 'Hello!' }],
  config: { provider: 'anthropic', model: 'claude-3-5-sonnet-latest' }
});

// Groq (fast inference)
const groqResponse = await client.completion({
  messages: [{ role: 'user', content: 'Hello!' }],
  config: { provider: 'groq', model: 'llama-3.1-70b-versatile' }
});
```

### Multi-turn Conversation

```typescript
const messages = [
  { role: 'user', content: 'My name is Alice' },
  { role: 'assistant', content: 'Nice to meet you, Alice! How can I help you today?' },
  { role: 'user', content: 'What is my name?' }
];

const response = await client.completion({ messages });
console.log(response.content); // "Your name is Alice."
```

### With System Prompt

```typescript
const response = await client.completion({
  messages: [
    { role: 'user', content: 'Write a poem' }
  ],
  systemPrompt: 'You are a creative poet who writes in haiku format.',
  config: {
    temperature: 0.9,  // Higher creativity
    maxTokens: 100
  }
});
```

### Error Handling

```typescript
try {
  const response = await client.completion({
    messages: [{ role: 'user', content: 'Hello!' }]
  });
  console.log(response.content);
} catch (error) {
  console.error('Ask API error:', error.message);
}
```

### Raw Stream Access

```typescript
// Get raw ReadableStream for custom processing
const rawStream = await client.getStream({
  messages: [{ role: 'user', content: 'Count to 10' }]
});

// Pipe to another destination
await rawStream.pipeTo(writableStream);
```

### List Available Models

```typescript
const { models, count } = await client.listModels();

console.log(`Available models: ${count}`);
Object.entries(models).forEach(([provider, providerModels]) => {
  console.log(`${provider}:`, providerModels.map(m => m.id));
});
```

### Health Check

```typescript
const health = await client.health();
console.log('API Status:', health.status);
console.log('Providers:', health.providers);
```

## Authentication

### Using Ask API Key (Recommended)

The simplest way to authenticate is using an Ask API key. This maps to your provider keys on the backend.

```typescript
const client = new AskClient({
  baseUrl: 'https://your-worker.workers.dev/v1',
  askApiKey: 'ask_abc123...'  // Single key for all providers
});
```

### Using Provider Keys Directly

Alternatively, you can pass provider keys directly:

```typescript
const client = new AskClient({
  baseUrl: 'https://your-worker.workers.dev/v1',
  providerKeys: {
    openai: 'sk-...',
    anthropic: 'sk-ant-...',
    groq: 'gsk_...'
  }
});
```

## TypeScript

The SDK is written in TypeScript and includes full type definitions:

```typescript
import { AskClient, IAskConfig, IMessage, ICompletionResponse } from '@livx.cc/ask/client';

const client: AskClient = new AskClient({
  baseUrl: 'https://api.example.com/v1',
  askApiKey: 'ask_123'
});

const messages: IMessage[] = [
  { role: 'user', content: 'Hello' }
];

const config: IAskConfig = {
  provider: 'openai',
  model: 'gpt-4',
  temperature: 0.7
};

const response: ICompletionResponse = await client.completion({
  messages,
  config
});
```

## React Example

```tsx
import { useState } from 'react';
import { AskClient } from '@livx.cc/ask/client';

const client = new AskClient({
  baseUrl: import.meta.env.VITE_ASK_API_URL,
  askApiKey: import.meta.env.VITE_ASK_API_KEY
});

function ChatComponent() {
  const [message, setMessage] = useState('');
  const [response, setResponse] = useState('');
  const [loading, setLoading] = useState(false);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);
    setResponse('');

    try {
      const stream = await client.streamCompletion({
        messages: [{ role: 'user', content: message }]
      });

      for await (const chunk of stream) {
        setResponse(prev => prev + chunk);
      }
    } catch (error) {
      console.error('Error:', error);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div>
      <form onSubmit={handleSubmit}>
        <input
          value={message}
          onChange={(e) => setMessage(e.target.value)}
          disabled={loading}
        />
        <button type="submit" disabled={loading}>
          {loading ? 'Thinking...' : 'Send'}
        </button>
      </form>
      <div>{response}</div>
    </div>
  );
}
```

## Vue Example

```vue
<template>
  <div>
    <form @submit.prevent="handleSubmit">
      <input v-model="message" :disabled="loading" />
      <button type="submit" :disabled="loading">
        {{ loading ? 'Thinking...' : 'Send' }}
      </button>
    </form>
    <div>{{ response }}</div>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import { AskClient } from '@livx.cc/ask/client';

const client = new AskClient({
  baseUrl: import.meta.env.VITE_ASK_API_URL,
  askApiKey: import.meta.env.VITE_ASK_API_KEY
});

const message = ref('');
const response = ref('');
const loading = ref(false);

async function handleSubmit() {
  loading.value = true;
  response.value = '';

  try {
    const stream = await client.streamCompletion({
      messages: [{ role: 'user', content: message.value }]
    });

    for await (const chunk of stream) {
      response.value += chunk;
    }
  } catch (error) {
    console.error('Error:', error);
  } finally {
    loading.value = false;
  }
}
</script>
```

## Browser Compatibility

The SDK uses modern browser APIs:
- `fetch` API
- `ReadableStream` API
- `async/await` and async iterators

Supported browsers:
- Chrome/Edge 80+
- Firefox 100+
- Safari 14.1+
- Opera 67+

## API Reference

### `AskClient`

Main client class for interacting with the Ask API.

#### Methods

##### `completion(params): Promise<ICompletionResponse>`

Execute a non-streaming completion request.

##### `streamCompletion(params): Promise<AsyncIterable<string>>`

Execute a streaming completion request. Returns an async iterable.

##### `getStream(params): Promise<ReadableStream<Uint8Array>>`

Get a raw ReadableStream for custom processing.

##### `listModels(): Promise<any>`

List all available models.

##### `health(): Promise<any>`

Check API health status.

## Support

- **Documentation**: See main [README.md](../../../README.md)
- **Issues**: [GitHub Issues](https://github.com/Livshitz/ask/issues)
- **License**: MIT

## License

MIT © Livshitz

