/**
 * Token counting and prompt truncation for AI integrations. A tokenizer is a
 * service because tokenization depends on the target model, provider, and
 * encoding rules rather than on Effect itself.
 *
 * **Mental model**
 *
 * {@link Tokenizer} provides two operations over prompt input: `tokenize`
 * converts a prompt into provider-specific token ids, and `truncate` keeps a
 * prompt within a token budget before it is sent to a model. Implementations
 * are usually provided by an AI provider package or by wrapping the tokenizer
 * used by the model.
 *
 * **Common tasks**
 *
 * - Count prompt tokens before choosing a model or request size
 * - Drop older conversation messages before a chat completion call
 * - Provide a custom tokenizer with {@link make} for tests or unsupported
 *   providers
 *
 * **Gotchas**
 *
 * - Token ids are model-specific. Counts from one tokenizer may not match
 *   another model's tokenizer.
 * - The `truncate` implementation produced by {@link make} keeps complete
 *   messages from the end of the prompt; it does not split a message to fit the
 *   remaining budget.
 * - Programs that access {@link Tokenizer} require a tokenizer service to be
 *   provided.
 *
 * **Example** (Providing a test tokenizer)
 *
 * ```ts
 * import { Effect } from "effect"
 * import { Tokenizer } from "effect/unstable/ai"
 *
 * const tokenizer = Tokenizer.make({
 *   tokenize: (prompt) =>
 *     Effect.succeed(prompt.content.map((_, index) => index))
 * })
 *
 * const program = Effect.gen(function*() {
 *   const service = yield* Tokenizer.Tokenizer
 *   const tokens = yield* service.tokenize("Count this prompt")
 *   return tokens.length
 * }).pipe(Effect.provideService(Tokenizer.Tokenizer, tokenizer))
 * ```
 *
 * @since 4.0.0
 */
import * as Context from "../../Context.js";
import * as Effect from "../../Effect.js";
import * as Predicate from "../../Predicate.js";
import * as Prompt from "./Prompt.js";
/**
 * Service tag for model tokenization services.
 *
 * **When to use**
 *
 * Use to access or provide model-specific token counting and prompt truncation
 * operations.
 *
 * **Details**
 *
 * This tag provides access to tokenization functionality throughout your
 * application, enabling token counting and prompt truncation capabilities.
 *
 * **Example** (Accessing the Tokenizer service)
 *
 * ```ts
 * import { Effect } from "effect"
 * import { Tokenizer } from "effect/unstable/ai"
 *
 * const useTokenizer = Effect.gen(function*() {
 *   const tokenizer = yield* Tokenizer.Tokenizer
 *   const tokens = yield* tokenizer.tokenize("Hello, world!")
 *   return tokens.length
 * })
 * ```
 *
 * @category services
 * @since 4.0.0
 */
export class Tokenizer extends /*#__PURE__*/Context.Service()("effect/ai/Tokenizer") {}
/**
 * Creates a Tokenizer service implementation from tokenization options.
 *
 * **Details**
 *
 * This function constructs a complete Tokenizer service by providing a
 * tokenization function. The service handles both tokenization and
 * truncation operations using the provided tokenizer.
 *
 * **Example** (Creating a word tokenizer)
 *
 * ```ts
 * import { Effect } from "effect"
 * import { Tokenizer } from "effect/unstable/ai"
 *
 * // Simple word-based tokenizer
 * const wordTokenizer = Tokenizer.make({
 *   tokenize: (prompt) =>
 *     Effect.succeed(
 *       prompt.content
 *         .flatMap((msg) =>
 *           typeof msg.content === "string"
 *             ? msg.content.split(" ")
 *             : msg.content.flatMap((part) =>
 *               part.type === "text" ? part.text.split(" ") : []
 *             )
 *         )
 *         .map((_, index) => index)
 *     )
 * })
 * ```
 *
 * @category constructors
 * @since 4.0.0
 */
export const make = options => Tokenizer.of({
  tokenize(input) {
    return options.tokenize(Prompt.make(input));
  },
  truncate(input, tokens) {
    return truncate(Prompt.make(input), options.tokenize, tokens);
  }
});
const truncate = (self, tokenize, maxTokens) => Effect.suspend(() => {
  let count = 0;
  let inputMessages = self.content;
  let outputMessages = [];
  const loop = Effect.suspend(() => {
    const message = inputMessages[inputMessages.length - 1];
    if (Predicate.isUndefined(message)) {
      return Effect.succeed(Prompt.fromMessages(outputMessages));
    }
    inputMessages = inputMessages.slice(0, inputMessages.length - 1);
    return Effect.flatMap(tokenize(Prompt.fromMessages([message])), tokens => {
      count += tokens.length;
      if (count > maxTokens) {
        return Effect.succeed(Prompt.fromMessages(outputMessages));
      }
      outputMessages = [message, ...outputMessages];
      return loop;
    });
  });
  return loop;
});
//# sourceMappingURL=Tokenizer.js.map