all: support openai-compatible models

The support is rather minimal at this point:
Only hard-coded models, only -unsafe, only -skabandaddr="".

The "shared" LLM package is strongly Claude-flavored.

We can fix all of this and more over time, if we are inspired to.
(Maybe we'll switch to https://github.com/maruel/genai?)

The goal for now is to get the rough structure in place.
I've rebased and rebuilt this more times than I care to remember.
diff --git a/llm/llm_string.go b/llm/llm_string.go
new file mode 100644
index 0000000..1c3189e
--- /dev/null
+++ b/llm/llm_string.go
@@ -0,0 +1,88 @@
+// Code generated by "stringer -type=MessageRole,ContentType,ToolChoiceType,StopReason -output=llm_string.go"; DO NOT EDIT.
+
+package llm
+
+import "strconv"
+
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[MessageRoleUser-0]
+	_ = x[MessageRoleAssistant-1]
+}
+
+const _MessageRole_name = "MessageRoleUserMessageRoleAssistant"
+
+var _MessageRole_index = [...]uint8{0, 15, 35}
+
+func (i MessageRole) String() string {
+	if i < 0 || i >= MessageRole(len(_MessageRole_index)-1) {
+		return "MessageRole(" + strconv.FormatInt(int64(i), 10) + ")"
+	}
+	return _MessageRole_name[_MessageRole_index[i]:_MessageRole_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[ContentTypeText-2]
+	_ = x[ContentTypeThinking-3]
+	_ = x[ContentTypeRedactedThinking-4]
+	_ = x[ContentTypeToolUse-5]
+	_ = x[ContentTypeToolResult-6]
+}
+
+const _ContentType_name = "ContentTypeTextContentTypeThinkingContentTypeRedactedThinkingContentTypeToolUseContentTypeToolResult"
+
+var _ContentType_index = [...]uint8{0, 15, 34, 61, 79, 100}
+
+func (i ContentType) String() string {
+	i -= 2
+	if i < 0 || i >= ContentType(len(_ContentType_index)-1) {
+		return "ContentType(" + strconv.FormatInt(int64(i+2), 10) + ")"
+	}
+	return _ContentType_name[_ContentType_index[i]:_ContentType_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[ToolChoiceTypeAuto-7]
+	_ = x[ToolChoiceTypeAny-8]
+	_ = x[ToolChoiceTypeNone-9]
+	_ = x[ToolChoiceTypeTool-10]
+}
+
+const _ToolChoiceType_name = "ToolChoiceTypeAutoToolChoiceTypeAnyToolChoiceTypeNoneToolChoiceTypeTool"
+
+var _ToolChoiceType_index = [...]uint8{0, 18, 35, 53, 71}
+
+func (i ToolChoiceType) String() string {
+	i -= 7
+	if i < 0 || i >= ToolChoiceType(len(_ToolChoiceType_index)-1) {
+		return "ToolChoiceType(" + strconv.FormatInt(int64(i+7), 10) + ")"
+	}
+	return _ToolChoiceType_name[_ToolChoiceType_index[i]:_ToolChoiceType_index[i+1]]
+}
+func _() {
+	// An "invalid array index" compiler error signifies that the constant values have changed.
+	// Re-run the stringer command to generate them again.
+	var x [1]struct{}
+	_ = x[StopReasonStopSequence-11]
+	_ = x[StopReasonMaxTokens-12]
+	_ = x[StopReasonEndTurn-13]
+	_ = x[StopReasonToolUse-14]
+}
+
+const _StopReason_name = "StopReasonStopSequenceStopReasonMaxTokensStopReasonEndTurnStopReasonToolUse"
+
+var _StopReason_index = [...]uint8{0, 22, 41, 58, 75}
+
+func (i StopReason) String() string {
+	i -= 11
+	if i < 0 || i >= StopReason(len(_StopReason_index)-1) {
+		return "StopReason(" + strconv.FormatInt(int64(i+11), 10) + ")"
+	}
+	return _StopReason_name[_StopReason_index[i]:_StopReason_index[i+1]]
+}