Custom & in-house clients: protect()
Bring an unsupported or in-house LLM client under TokenPolice. In Node, objectPath is a string array.
If you call a provider or an in-house wrapper that the auto-instrumentation doesn't already cover,
protect() wraps one method so it gets the same pre-flight check and telemetry as a built-in
client. Like everything else, it's fail-open: if the wrap fails, your call runs unchanged.
Python
import token_police as tp
tp.protect(
"my_company.llm", # module path
"InHouseClient", # class name
"complete", # method name
True, # is it async?
manual=True, # required for the provider hint to take effect
provider="openai", # hint how to parse usage
)Signature:
protect(module_path, class_name, method_name, is_async, *,
manual=False, provider=None, target_object=None)manual=True— for clients that don't emit OpenTelemetry spans; TokenPolice records usage itself.provider=...— a hint (e.g."openai","anthropic") that drives usage and cost parsing.target_object=...— pass a live object to wrap instead of resolving it by import.
provider= only takes effect with manual=True. The two are gated together, so
provider="openai" on its own is silently ignored — no error, no warning — and the wrapper
falls back to guessing the provider from the module path, which for an in-house module usually
guesses nothing. The result is a call that is metered with no usable usage parsing. If you are
passing provider=, pass manual=True with it.
Node
The shape matches, with one difference to watch: the second argument, objectPath, is a
string[] — the path of property names walked from the module root, not a single string.
import * as tp from "token-police";
tp.protect(
"my-company-llm", // module name
["InHouseClient", "prototype"], // objectPath — an ARRAY, walked from the module root
"complete", // method name
true, // is it async?
{ manual: true, provider: "openai" },// options
);Signature:
protect(moduleName, objectPath: string[], methodName, isAsync, options?: {
manual?: boolean;
provider?: string;
streaming?: boolean;
module?: unknown; // pass a live module/object instead of resolving by name
})objectPath is an array, and it must end on the object that actually owns the method.
TokenPolice walks the path property by property from the module root and then reads
obj[methodName] — there is no class-vs-instance magic.
So for class InHouseClient { async complete() {…} }, the method lives on
InHouseClient.prototype, and the path is ["InHouseClient", "prototype"]. Passing
["InHouseClient"] looks for a static InHouseClient.complete, doesn't find one, and the
wrap is skipped with a startup warning — your calls run, unmetered and unchecked. Use
["InHouseClient"] only when the method really is static, and a bare "InHouseClient" string
is never right. A nested target is just a longer walk: ["outer", "Inner", "prototype"].

