NewModelFactory returns a [HandlerFactory] for [HookTypeModel] backed by client. Register it with [Registry.Register] in the runtime to enable `type: model` hooks. The returned factory pre-parses the prompt template at factory time so syntax errors surface at registry-lookup, not on the first hook
(client ModelClient)
| 117 | // invocation. The shape/schema lookup happens at handler-construction |
| 118 | // time so adding new shapes after factory registration still works. |
| 119 | func NewModelFactory(client ModelClient) HandlerFactory { |
| 120 | if client == nil { |
| 121 | // The empty client always errors; this lets a runtime register |
| 122 | // the factory without a credentialed client and fail at the |
| 123 | // first use rather than at construction. |
| 124 | client = nilClient{} |
| 125 | } |
| 126 | return func(_ HandlerEnv, hook Hook) (Handler, error) { |
| 127 | if hook.Model == "" { |
| 128 | return nil, errors.New("model hook requires a non-empty model") |
| 129 | } |
| 130 | if hook.Prompt == "" { |
| 131 | return nil, errors.New("model hook requires a non-empty prompt") |
| 132 | } |
| 133 | shape, ok := lookupShape(hook.Schema) |
| 134 | if !ok { |
| 135 | return nil, fmt.Errorf("model hook: unknown schema %q (register one with hooks.RegisterResponseShape)", hook.Schema) |
| 136 | } |
| 137 | tpl, err := template.New("hook-prompt").Funcs(promptFuncs).Parse(hook.Prompt) |
| 138 | if err != nil { |
| 139 | return nil, fmt.Errorf("model hook: parse prompt template: %w", err) |
| 140 | } |
| 141 | return &modelHandler{ |
| 142 | client: client, |
| 143 | model: hook.Model, |
| 144 | tpl: tpl, |
| 145 | schema: lookupSchema(hook.Schema), |
| 146 | shape: shape, |
| 147 | }, nil |
| 148 | } |
| 149 | } |
| 150 | |
| 151 | // promptFuncs is the [text/template] FuncMap exposed to model-hook |
| 152 | // prompts. Kept small on purpose: anything more elaborate belongs in |