From 5cd1b049d770256e5dfcc9cc17193b6a35910cd3 Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Tue, 9 Dec 2025 12:38:42 +0000 Subject: [PATCH 1/2] add nested fixture template --- test/approvals/echo_2_repli_new | 1 + test/approvals/repli_template_list | 1 + test/helper.bash | 3 ++- 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/test/approvals/echo_2_repli_new b/test/approvals/echo_2_repli_new index 1116a0e..a4910f5 100644 --- a/test/approvals/echo_2_repli_new +++ b/test/approvals/echo_2_repli_new @@ -3,5 +3,6 @@ Templates in tmp/templates: 1. another 2. model1 3. model2 + 4. nested/upscaler • info • repli_new_command → copying tmp/templates/model1.yaml → tmp/repli.yaml diff --git a/test/approvals/repli_template_list b/test/approvals/repli_template_list index f8cfebb..d64afb3 100644 --- a/test/approvals/repli_template_list +++ b/test/approvals/repli_template_list @@ -3,3 +3,4 @@ Templates in tmp/templates: 1. another 2. model1 3. model2 + 4. nested/upscaler diff --git a/test/helper.bash b/test/helper.bash index 57f6505..a9d9584 100644 --- a/test/helper.bash +++ b/test/helper.bash @@ -14,11 +14,12 @@ reset_state() { # add dummy templates to the templates dir add_templates() { blue_bold " adding dummy templates" - mkdir -p "$REPLI_TEMPLATES_DIR" + mkdir -p "$REPLI_TEMPLATES_DIR/nested" cp fixtures/templates/basic.yaml "$REPLI_TEMPLATES_DIR/model1.yaml" cp fixtures/templates/basic.yaml "$REPLI_TEMPLATES_DIR/model2.yaml" cp fixtures/templates/basic.yaml "$REPLI_TEMPLATES_DIR/another.yaml" + cp fixtures/templates/basic.yaml "$REPLI_TEMPLATES_DIR/nested/upscaler.yaml" } add_repli_yaml() { From b74d6e65d4a5fa6f4aa1a39093fad3afdd8f13ab Mon Sep 17 00:00:00 2001 From: Danny Ben Shitrit Date: Tue, 9 Dec 2025 13:41:07 +0000 Subject: [PATCH 2/2] - Add support for unofficial models --- repli | 63 ++++++++++++------- src/commands/template/new.sh | 6 +- src/lib/get_example_from_replicate.sh | 22 +++++-- src/lib/yurl.sh | 35 ++++++----- test/approvals/repli_info_google_nano_banana | 1 + .../repli_info_google_nano_banana_json | 1 + .../repli_template_new_google_nano_banana | 1 + ...epli_template_new_google_nano_banana_force | 1 + ...emplate_new_google_nano_banana_name_banana | 1 + .../repli_template_new_unofficial_mymodel | 4 ++ .../v1/models/google/nano-banana/get.json | 1 + .../google/nano-banana/predictions/post.json | 2 +- .../v1/models/unofficial/mymodel/get.json | 26 ++++++++ .../unofficial/mymodel/predictions/post.json | 15 +++++ test/spec/template/new.sh | 2 + 15 files changed, 132 insertions(+), 49 deletions(-) create mode 100644 test/approvals/repli_template_new_unofficial_mymodel create mode 100644 test/mockserver/mocks/v1/models/unofficial/mymodel/get.json create mode 100644 test/mockserver/mocks/v1/models/unofficial/mymodel/predictions/post.json diff --git a/repli b/repli index 176e259..b584ec2 100755 --- a/repli +++ b/repli @@ -815,10 +815,24 @@ filter_templates_dir_exists() { get_example_from_replicate() { json=$(get_model_info "$model") || return 1 - jq --arg model "$model" \ - '{model: $model, input: .default_example.input}' \ - <<<"$json" | - yq -P - + # Determine whether the model is official + is_official=$(jq -r '.is_official // false' <<<"$json") + + if [[ "$is_official" == "true" ]]; then + log debug "model status: $(blue official)" + # Official image + jq --arg model "$model" \ + '{model: $model, input: .default_example.input}' \ + <<<"$json" | yq -P - + else + # Non-official image: use version instead of model + log debug "model status: $(blue unofficial)" + version=$(jq -r '.latest_version.id' <<<"$json") + + jq --arg model "$model" --arg version "$version" \ + '{version: ($model + ":" + $version), input: .default_example.input}' \ + <<<"$json" | yq -P - + fi } # src/lib/get_file_url.sh @@ -1165,40 +1179,41 @@ verify_success_json() { # src/lib/yurl.sh yurl() { local yaml_file="$1" - local model json + local model version json payload url # YAML validation - if ! yq -e '.model' "$yaml_file" >/dev/null 2>&1; then - log error "no model field in $(blue "$yaml_file")" - return 1 - fi - if ! yq -e '.input' "$yaml_file" >/dev/null 2>&1; then log error "no input field in $(blue "$yaml_file")" return 1 fi - # Get the model - model=$(yq -r '.model' "$yaml_file") + model=$(yq -r '.model // ""' "$yaml_file") + version=$(yq -r '.version // ""' "$yaml_file") - # Convert .input YAML → JSON - json=$(yq -o=json '.input' "$yaml_file") + if [[ -z "$model" && -z "$version" ]]; then + log error "no model or version field in $(blue "$yaml_file")" + return 1 + fi - # Replace any "" placeholders - json=$(replace_file_placeholders "$json") + if [[ -n "$model" ]]; then + url="$replicate_host/v1/models/${model}/predictions" + else + url="$replicate_host/v1/predictions" + fi - # Replace any embedded "@filename" markers - json=$(replace_embed_markers "$json") + # Convert .input YAML → JSON and replace and ~filename.txt + payload=$(yq -o=json '.' "$yaml_file") + payload=$(replace_file_placeholders "$payload") + payload=$(replace_embed_markers "$payload") # Pipe the evaluated JSON to curl which uses it (@-) as its data. - echo "$json" | - jq '{input: .}' | + echo "$payload" | curl -sS -X POST \ -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ -H "Content-Type: application/json" \ -H "Prefer: wait" \ -d @- \ - "$replicate_host/v1/models/${model}/predictions" + "$url" } # :command.command_functions @@ -1326,9 +1341,9 @@ repli_info_command() { repli_template_new_command() { # src/commands/template/new.sh - model="${args[model]}" - name="${args[--name]}" - force="${args[--force]}" + local model="${args[model]}" + local name="${args[--name]}" + local force="${args[--force]}" [[ "$name" == "auto" ]] && name="${model#*/}" diff --git a/src/commands/template/new.sh b/src/commands/template/new.sh index 12704c5..80ba412 100644 --- a/src/commands/template/new.sh +++ b/src/commands/template/new.sh @@ -1,6 +1,6 @@ -model="${args[model]}" -name="${args[--name]}" -force="${args[--force]}" +local model="${args[model]}" +local name="${args[--name]}" +local force="${args[--force]}" [[ "$name" == "auto" ]] && name="${model#*/}" diff --git a/src/lib/get_example_from_replicate.sh b/src/lib/get_example_from_replicate.sh index b900563..1c687b3 100644 --- a/src/lib/get_example_from_replicate.sh +++ b/src/lib/get_example_from_replicate.sh @@ -1,8 +1,22 @@ get_example_from_replicate() { json=$(get_model_info "$model") || return 1 - jq --arg model "$model" \ - '{model: $model, input: .default_example.input}' \ - <<<"$json" | - yq -P - + # Determine whether the model is official + is_official=$(jq -r '.is_official // false' <<<"$json") + + if [[ "$is_official" == "true" ]]; then + log debug "model status: $(blue official)" + # Official image + jq --arg model "$model" \ + '{model: $model, input: .default_example.input}' \ + <<<"$json" | yq -P - + else + # Non-official image: use version instead of model + log debug "model status: $(blue unofficial)" + version=$(jq -r '.latest_version.id' <<<"$json") + + jq --arg model "$model" --arg version "$version" \ + '{version: ($model + ":" + $version), input: .default_example.input}' \ + <<<"$json" | yq -P - + fi } diff --git a/src/lib/yurl.sh b/src/lib/yurl.sh index 57b71c9..82715f0 100644 --- a/src/lib/yurl.sh +++ b/src/lib/yurl.sh @@ -2,38 +2,39 @@ ## usage: yurl request.yaml yurl() { local yaml_file="$1" - local model json + local model version json payload url # YAML validation - if ! yq -e '.model' "$yaml_file" >/dev/null 2>&1; then - log error "no model field in $(blue "$yaml_file")" - return 1 - fi - if ! yq -e '.input' "$yaml_file" >/dev/null 2>&1; then log error "no input field in $(blue "$yaml_file")" return 1 fi - # Get the model - model=$(yq -r '.model' "$yaml_file") + model=$(yq -r '.model // ""' "$yaml_file") + version=$(yq -r '.version // ""' "$yaml_file") - # Convert .input YAML → JSON - json=$(yq -o=json '.input' "$yaml_file") + if [[ -z "$model" && -z "$version" ]]; then + log error "no model or version field in $(blue "$yaml_file")" + return 1 + fi - # Replace any "" placeholders - json=$(replace_file_placeholders "$json") + if [[ -n "$model" ]]; then + url="$replicate_host/v1/models/${model}/predictions" + else + url="$replicate_host/v1/predictions" + fi - # Replace any embedded "@filename" markers - json=$(replace_embed_markers "$json") + # Convert .input YAML → JSON and replace and ~filename.txt + payload=$(yq -o=json '.' "$yaml_file") + payload=$(replace_file_placeholders "$payload") + payload=$(replace_embed_markers "$payload") # Pipe the evaluated JSON to curl which uses it (@-) as its data. - echo "$json" | - jq '{input: .}' | + echo "$payload" | curl -sS -X POST \ -H "Authorization: Bearer $REPLICATE_API_TOKEN" \ -H "Content-Type: application/json" \ -H "Prefer: wait" \ -d @- \ - "$replicate_host/v1/models/${model}/predictions" + "$url" } diff --git a/test/approvals/repli_info_google_nano_banana b/test/approvals/repli_info_google_nano_banana index 1f0b47d..a6a97c7 100644 --- a/test/approvals/repli_info_google_nano_banana +++ b/test/approvals/repli_info_google_nano_banana @@ -3,6 +3,7 @@ default_example: input: prompt: an image of a JSON file output_format: jpg +is_official: true latest_version: openapi_schema: components: diff --git a/test/approvals/repli_info_google_nano_banana_json b/test/approvals/repli_info_google_nano_banana_json index 320ca56..f918235 100644 --- a/test/approvals/repli_info_google_nano_banana_json +++ b/test/approvals/repli_info_google_nano_banana_json @@ -6,6 +6,7 @@ "output_format": "jpg" } }, + "is_official": true, "latest_version": { "openapi_schema": { "components": { diff --git a/test/approvals/repli_template_new_google_nano_banana b/test/approvals/repli_template_new_google_nano_banana index d96e0f1..1f1f446 100644 --- a/test/approvals/repli_template_new_google_nano_banana +++ b/test/approvals/repli_template_new_google_nano_banana @@ -1,3 +1,4 @@ • debug • repli_template_new_command → fetching example for google/nano-banana • debug • get_model_info → calling replicate API +• debug • get_example_from_replicate → model status: official • info • repli_template_new_command → saving to tmp/templates/nano-banana.yaml diff --git a/test/approvals/repli_template_new_google_nano_banana_force b/test/approvals/repli_template_new_google_nano_banana_force index d96e0f1..1f1f446 100644 --- a/test/approvals/repli_template_new_google_nano_banana_force +++ b/test/approvals/repli_template_new_google_nano_banana_force @@ -1,3 +1,4 @@ • debug • repli_template_new_command → fetching example for google/nano-banana • debug • get_model_info → calling replicate API +• debug • get_example_from_replicate → model status: official • info • repli_template_new_command → saving to tmp/templates/nano-banana.yaml diff --git a/test/approvals/repli_template_new_google_nano_banana_name_banana b/test/approvals/repli_template_new_google_nano_banana_name_banana index 42d809d..d3ee577 100644 --- a/test/approvals/repli_template_new_google_nano_banana_name_banana +++ b/test/approvals/repli_template_new_google_nano_banana_name_banana @@ -1,3 +1,4 @@ • debug • repli_template_new_command → fetching example for google/nano-banana • debug • get_model_info → calling replicate API +• debug • get_example_from_replicate → model status: official • info • repli_template_new_command → saving to tmp/templates/banana.yaml diff --git a/test/approvals/repli_template_new_unofficial_mymodel b/test/approvals/repli_template_new_unofficial_mymodel new file mode 100644 index 0000000..8f7f506 --- /dev/null +++ b/test/approvals/repli_template_new_unofficial_mymodel @@ -0,0 +1,4 @@ +• debug • repli_template_new_command → fetching example for unofficial/mymodel +• debug • get_model_info → calling replicate API +• debug • get_example_from_replicate → model status: unofficial +• info • repli_template_new_command → saving to tmp/templates/mymodel.yaml diff --git a/test/mockserver/mocks/v1/models/google/nano-banana/get.json b/test/mockserver/mocks/v1/models/google/nano-banana/get.json index 34ca21d..df83ad2 100644 --- a/test/mockserver/mocks/v1/models/google/nano-banana/get.json +++ b/test/mockserver/mocks/v1/models/google/nano-banana/get.json @@ -5,6 +5,7 @@ "output_format": "jpg" } }, + "is_official": true, "latest_version": { "openapi_schema": { "components": { diff --git a/test/mockserver/mocks/v1/models/google/nano-banana/predictions/post.json b/test/mockserver/mocks/v1/models/google/nano-banana/predictions/post.json index 1fb6e23..5247e81 100644 --- a/test/mockserver/mocks/v1/models/google/nano-banana/predictions/post.json +++ b/test/mockserver/mocks/v1/models/google/nano-banana/predictions/post.json @@ -1,6 +1,6 @@ { "id": "123", - "model": "google/nano-banana", + "model": "unofficial/mymodel", "input": { "output_format": "jpg", diff --git a/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json b/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json new file mode 100644 index 0000000..29612c6 --- /dev/null +++ b/test/mockserver/mocks/v1/models/unofficial/mymodel/get.json @@ -0,0 +1,26 @@ +{ + "default_example": { + "input": { + "prompt": "an image of a JSON file", + "output_format": "jpg" + } + }, + "is_official": false, + "latest_version": { + "id": "660d922d33153019e8c263a3bba265de882e7f4f70396546b6c9c8f9d47a021a", + "openapi_schema": { + "components": { + "schemas": { + "Input": { + "type": "object", + "title": "Input", + "required": [ + "prompt" + ], + "properties": "... trimmed ..." + } + } + } + } + } +} diff --git a/test/mockserver/mocks/v1/models/unofficial/mymodel/predictions/post.json b/test/mockserver/mocks/v1/models/unofficial/mymodel/predictions/post.json new file mode 100644 index 0000000..1fb6e23 --- /dev/null +++ b/test/mockserver/mocks/v1/models/unofficial/mymodel/predictions/post.json @@ -0,0 +1,15 @@ +{ + "id": "123", + "model": "google/nano-banana", + "input": + { + "output_format": "jpg", + "prompt": "an image of a JSON file" + }, + "output": + [ + "http://localhost:3000/assets/out-0.jpg" + ], + "error": null, + "status": "succeeded" +} \ No newline at end of file diff --git a/test/spec/template/new.sh b/test/spec/template/new.sh index 15ecc9e..df8a345 100644 --- a/test/spec/template/new.sh +++ b/test/spec/template/new.sh @@ -10,3 +10,5 @@ describe "templates new" approve "cat $REPLI_TEMPLATES_DIR/banana.yaml" "cat_banana_yaml" approve "ls $REPLI_TEMPLATES_DIR" + context "when adding unofficial models" + approve "repli template new unofficial/mymodel"